12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177 |
- {**************************************************************************************************}
- { }
- { Project JEDI Code Library (JCL) }
- { }
- { The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
- { you may not use this file except in compliance with the License. You may obtain a copy of the }
- { License at http://www.mozilla.org/MPL/ }
- { }
- { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF }
- { ANY KIND, either express or implied. See the License for the specific language governing rights }
- { and limitations under the License. }
- { }
- { The Original Code is JclDebug.pas. }
- { }
- { The Initial Developers of the Original Code are Petr Vones and Marcel van Brakel. }
- { Portions created by these individuals are Copyright (C) of these individuals. }
- { All Rights Reserved. }
- { }
- { Contributor(s): }
- { Marcel van Brakel }
- { Flier Lu (flier) }
- { Florent Ouchet (outchy) }
- { Robert Marquardt (marquardt) }
- { Robert Rossmair (rrossmair) }
- { Andreas Hausladen (ahuser) }
- { Petr Vones (pvones) }
- { Soeren Muehlbauer }
- { Uwe Schuster (uschuster) }
- { }
- {**************************************************************************************************}
- { }
- { Various debugging support routines and classes. This includes: Diagnostics routines, Trace }
- { routines, Stack tracing and Source Locations a la the C/C++ __FILE__ and __LINE__ macros. }
- { }
- {**************************************************************************************************}
- { }
- { Last modified: $Date:: $ }
- { Revision: $Rev:: $ }
- { Author: $Author:: $ }
- { }
- {**************************************************************************************************}
- unit JclDebug;
- interface
- {$I jcl.inc}
- {$I windowsonly.inc}
- uses
- {$IFDEF UNITVERSIONING}
- JclUnitVersioning,
- {$ENDIF UNITVERSIONING}
- {$IFDEF HAS_UNITSCOPE}
- {$IFDEF MSWINDOWS}
- Winapi.Windows,
- {$ENDIF MSWINDOWS}
- System.Classes, System.SysUtils, System.Contnrs,
- {$ELSE ~HAS_UNITSCOPE}
- {$IFDEF MSWINDOWS}
- Windows,
- {$ENDIF MSWINDOWS}
- Classes, SysUtils, Contnrs,
- {$ENDIF ~HAS_UNITSCOPE}
- JclBase, JclFileUtils, JclPeImage,
- {$IFDEF BORLAND}
- {$IFNDEF WINSCP}
- JclTD32,
- {$ENDIF ~WINSCP}
- {$ENDIF BORLAND}
- JclSynch;
- // Diagnostics
- procedure AssertKindOf(const ClassName: string; const Obj: TObject); overload;
- procedure AssertKindOf(const ClassType: TClass; const Obj: TObject); overload;
- // use TraceMsg
- // procedure Trace(const Msg: string);
- procedure TraceMsg(const Msg: string);
- {$IFNDEF WINSCP}
- procedure TraceFmt(const Fmt: string; const Args: array of const);
- {$ENDIF}
- procedure TraceLoc(const Msg: string);
- procedure TraceLocFmt(const Fmt: string; const Args: array of const);
- // Optimized functionality of JclSysInfo functions ModuleFromAddr and IsSystemModule
- type
- TJclModuleInfo = class(TObject)
- private
- FSize: Cardinal;
- FEndAddr: Pointer;
- FStartAddr: Pointer;
- FSystemModule: Boolean;
- public
- property EndAddr: Pointer read FEndAddr;
- property Size: Cardinal read FSize;
- property StartAddr: Pointer read FStartAddr;
- property SystemModule: Boolean read FSystemModule;
- end;
- TJclModuleInfoList = class(TObjectList)
- private
- FDynamicBuild: Boolean;
- FSystemModulesOnly: Boolean;
- FRefCount: Integer;
- function GetItems(Index: TJclListSize): TJclModuleInfo;
- function GetModuleFromAddress(Addr: Pointer): TJclModuleInfo;
- protected
- procedure BuildModulesList;
- function CreateItemForAddress(Addr: Pointer; SystemModule: Boolean): TJclModuleInfo;
- public
- constructor Create(ADynamicBuild, ASystemModulesOnly: Boolean);
- function AddModule(Module: HMODULE; SystemModule: Boolean): Boolean;
- function IsSystemModuleAddress(Addr: Pointer): Boolean;
- function IsValidModuleAddress(Addr: Pointer): Boolean;
- property DynamicBuild: Boolean read FDynamicBuild;
- property Items[Index: TJclListSize]: TJclModuleInfo read GetItems;
- property ModuleFromAddress[Addr: Pointer]: TJclModuleInfo read GetModuleFromAddress;
- end;
- function JclValidateModuleAddress(Addr: Pointer): Boolean;
- // MAP file abstract parser
- type
- PJclMapAddress = ^TJclMapAddress;
- TJclMapAddress = packed record
- Segment: Word;
- Offset: TJclAddr;
- end;
- PJclMapString = PAnsiChar;
- TJclAbstractMapParser = class(TObject)
- private
- FLinkerBug: Boolean;
- FLinkerBugUnitName: PJclMapString;
- FStream: TJclFileMappingStream;
- function GetLinkerBugUnitName: string;
- protected
- FModule: HMODULE;
- FLastUnitName: PJclMapString;
- FLastUnitFileName: PJclMapString;
- procedure ClassTableItem(const Address: TJclMapAddress; Len: Integer; SectionName, GroupName: PJclMapString); virtual; abstract;
- procedure SegmentItem(const Address: TJclMapAddress; Len: Integer; GroupName, UnitName: PJclMapString); virtual; abstract;
- function CanHandlePublicsByName: Boolean; virtual; abstract;
- function CanHandlePublicsByValue: Boolean; virtual; abstract;
- procedure PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString); virtual; abstract;
- procedure PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString); virtual; abstract;
- procedure LineNumberUnitItem(UnitName, UnitFileName: PJclMapString); virtual; abstract;
- procedure LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress); virtual; abstract;
- public
- constructor Create(const MapFileName: TFileName; Module: HMODULE); overload; virtual;
- constructor Create(const MapFileName: TFileName); overload;
- destructor Destroy; override;
- procedure Parse;
- class function MapStringToFileName(MapString: PJclMapString): string;
- class function MapStringToModuleName(MapString: PJclMapString): string;
- class function MapStringToStr(MapString: PJclMapString; IgnoreSpaces: Boolean = False): string;
- property LinkerBug: Boolean read FLinkerBug;
- property LinkerBugUnitName: string read GetLinkerBugUnitName;
- property Stream: TJclFileMappingStream read FStream;
- end;
- // MAP file parser
- TJclMapClassTableEvent = procedure(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const SectionName, GroupName: string) of object;
- TJclMapSegmentEvent = procedure(Sender: TObject; const Address: TJclMapAddress; Len: Integer; const GroupName, UnitName: string) of object;
- TJclMapPublicsEvent = procedure(Sender: TObject; const Address: TJclMapAddress; const Name: string) of object;
- TJclMapLineNumberUnitEvent = procedure(Sender: TObject; const UnitName, UnitFileName: string) of object;
- TJclMapLineNumbersEvent = procedure(Sender: TObject; LineNumber: Integer; const Address: TJclMapAddress) of object;
- TJclMapParser = class(TJclAbstractMapParser)
- private
- FOnClassTable: TJclMapClassTableEvent;
- FOnLineNumbers: TJclMapLineNumbersEvent;
- FOnLineNumberUnit: TJclMapLineNumberUnitEvent;
- FOnPublicsByValue: TJclMapPublicsEvent;
- FOnPublicsByName: TJclMapPublicsEvent;
- FOnSegmentItem: TJclMapSegmentEvent;
- protected
- procedure ClassTableItem(const Address: TJclMapAddress; Len: Integer; SectionName, GroupName: PJclMapString); override;
- procedure SegmentItem(const Address: TJclMapAddress; Len: Integer; GroupName, UnitName: PJclMapString); override;
- function CanHandlePublicsByName: Boolean; override;
- function CanHandlePublicsByValue: Boolean; override;
- procedure PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString); override;
- procedure PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString); override;
- procedure LineNumberUnitItem(UnitName, UnitFileName: PJclMapString); override;
- procedure LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress); override;
- public
- property OnClassTable: TJclMapClassTableEvent read FOnClassTable write FOnClassTable;
- property OnSegment: TJclMapSegmentEvent read FOnSegmentItem write FOnSegmentItem;
- property OnPublicsByName: TJclMapPublicsEvent read FOnPublicsByName write FOnPublicsByName;
- property OnPublicsByValue: TJclMapPublicsEvent read FOnPublicsByValue write FOnPublicsByValue;
- property OnLineNumberUnit: TJclMapLineNumberUnitEvent read FOnLineNumberUnit write FOnLineNumberUnit;
- property OnLineNumbers: TJclMapLineNumbersEvent read FOnLineNumbers write FOnLineNumbers;
- end;
- TJclMapStringCache = record
- CachedValue: string;
- RawValue: PJclMapString;
- TLS: Boolean;
- end;
- // MAP file scanner
- PJclMapSegmentClass = ^TJclMapSegmentClass;
- TJclMapSegmentClass = record
- Segment: Word; // segment ID
- Start: DWORD; // start as in the map file
- Addr: DWORD; // start as in process memory
- VA: DWORD; // position relative to module base adress
- Len: DWORD; // segment length
- SectionName: TJclMapStringCache;
- GroupName: TJclMapStringCache;
- end;
- PJclMapSegment = ^TJclMapSegment;
- TJclMapSegment = record
- Segment: Word;
- StartVA: DWORD; // VA relative to (module base address + $10000)
- EndVA: DWORD;
- UnitName: TJclMapStringCache;
- end;
- PJclMapProcName = ^TJclMapProcName;
- TJclMapProcName = record
- Segment: Word;
- VA: DWORD; // VA relative to (module base address + $10000)
- ProcName: TJclMapStringCache;
- end;
- PJclMapLineNumber = ^TJclMapLineNumber;
- TJclMapLineNumber = record
- Segment: Word;
- VA: DWORD; // VA relative to (module base address + $10000)
- LineNumber: Integer;
- UnitName: PJclMapString;
- end;
- TJclMapScanner = class(TJclAbstractMapParser)
- private
- FSegmentClasses: array of TJclMapSegmentClass;
- FLineNumbers: array of TJclMapLineNumber;
- FProcNames: array of TJclMapProcName;
- FSegments: array of TJclMapSegment;
- FSourceNames: array of TJclMapProcName;
- FLineNumbersCnt: Integer;
- FLineNumberErrors: Integer;
- FNewUnitFileName: PJclMapString;
- FCurrentUnitName: PJclMapString;
- FProcNamesCnt: Integer;
- FSegmentCnt: Integer;
- FLastAccessedSegementIndex: Integer;
- function IndexOfSegment(Addr: DWORD): Integer;
- protected
- function MAPAddrToVA(const Addr: DWORD): DWORD;
- procedure ClassTableItem(const Address: TJclMapAddress; Len: Integer; SectionName, GroupName: PJclMapString); override;
- procedure SegmentItem(const Address: TJclMapAddress; Len: Integer; GroupName, UnitName: PJclMapString); override;
- function CanHandlePublicsByName: Boolean; override;
- function CanHandlePublicsByValue: Boolean; override;
- procedure PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString); override;
- procedure PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString); override;
- procedure LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress); override;
- procedure LineNumberUnitItem(UnitName, UnitFileName: PJclMapString); override;
- procedure Scan;
- function GetLineNumberByIndex(Index: Integer): TJCLMapLineNumber;
- public
- constructor Create(const MapFileName: TFileName; Module: HMODULE); override;
- class function MapStringCacheToFileName(var MapString: TJclMapStringCache): string;
- class function MapStringCacheToModuleName(var MapString: TJclMapStringCache): string;
- class function MapStringCacheToStr(var MapString: TJclMapStringCache; IgnoreSpaces: Boolean = False): string;
- // Addr are virtual addresses relative to (module base address + $10000)
- function LineNumberFromAddr(Addr: DWORD): Integer; overload;
- function LineNumberFromAddr(Addr: DWORD; out Offset: Integer): Integer; overload;
- function ModuleNameFromAddr(Addr: DWORD): string;
- function ModuleStartFromAddr(Addr: DWORD): DWORD;
- function ProcNameFromAddr(Addr: DWORD): string; overload;
- function ProcNameFromAddr(Addr: DWORD; out Offset: Integer): string; overload;
- function SourceNameFromAddr(Addr: DWORD): string;
- function VAFromUnitAndProcName(const UnitName, ProcName: string): DWORD;
- property LineNumberErrors: Integer read FLineNumberErrors;
- property LineNumbersCnt: Integer read FLineNumbersCnt;
- property LineNumberByIndex[Index: Integer]: TJclMapLineNumber read GetLineNumberByIndex;
- end;
- type
- PJclDbgHeader = ^TJclDbgHeader;
- TJclDbgHeader = packed record
- Signature: DWORD;
- Version: Byte;
- Units: Integer;
- SourceNames: Integer;
- Symbols: Integer;
- LineNumbers: Integer;
- Words: Integer;
- ModuleName: Integer;
- CheckSum: Integer;
- CheckSumValid: Boolean;
- end;
- TJclBinDebugGenerator = class(TJclMapScanner)
- private
- FDataStream: TMemoryStream;
- FMapFileName: TFileName;
- protected
- procedure CreateData;
- public
- constructor Create(const MapFileName: TFileName; Module: HMODULE); override;
- destructor Destroy; override;
- function CalculateCheckSum: Boolean;
- property DataStream: TMemoryStream read FDataStream;
- end;
- TJclBinDbgNameCache = record
- Addr: DWORD;
- FirstWord: Integer;
- SecondWord: Integer;
- Text: string;
- end;
- TJclBinDebugScanner = class(TObject)
- private
- FCacheData: Boolean;
- FCacheProcNames: Boolean;
- FStream: TCustomMemoryStream;
- FValidFormat: Boolean;
- FLineNumbers: array of TJclMapLineNumber;
- FProcNames: array of TJclBinDbgNameCache;
- function GetModuleName: string;
- protected
- procedure CacheLineNumbers;
- procedure CacheProcNames;
- procedure CheckFormat;
- function DataToStr(A: Integer): string;
- function MakePtr(A: Integer): Pointer;
- class function ReadValue(var P: Pointer; var Value: Integer): Boolean; {$IFDEF SUPPORTS_STATIC}static;{$ENDIF}
- public
- constructor Create(AStream: TCustomMemoryStream; CacheData, CacheProcNames: Boolean);
- function IsModuleNameValid(const Name: TFileName): Boolean;
- function LineNumberFromAddr(Addr: DWORD): Integer; overload;
- function LineNumberFromAddr(Addr: DWORD; out Offset: Integer): Integer; overload;
- function ProcNameFromAddr(Addr: DWORD): string; overload;
- function ProcNameFromAddr(Addr: DWORD; out Offset: Integer): string; overload;
- function ModuleNameFromAddr(Addr: DWORD): string;
- function ModuleStartFromAddr(Addr: DWORD): DWORD;
- function SourceNameFromAddr(Addr: DWORD): string;
- property ModuleName: string read GetModuleName;
- property ValidFormat: Boolean read FValidFormat;
- function VAFromUnitAndProcName(const UnitName, ProcName: string): DWORD;
- end;
- function ConvertMapFileToJdbgFile(const MapFileName: TFileName): Boolean; overload;
- function ConvertMapFileToJdbgFile(const MapFileName: TFileName; out LinkerBugUnit: string;
- out LineNumberErrors: Integer): Boolean; overload;
- function ConvertMapFileToJdbgFile(const MapFileName: TFileName; out LinkerBugUnit: string;
- out LineNumberErrors, MapFileSize, JdbgFileSize: Integer): Boolean; overload;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName,
- MapFileName: TFileName; out LinkerBugUnit: string;
- out MapFileSize, JclDebugDataSize: Integer): Boolean; overload;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName,
- MapFileName: TFileName; out LinkerBugUnit: string;
- out MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean; overload;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
- BinDebug: TJclBinDebugGenerator; out LinkerBugUnit: string;
- out MapFileSize, JclDebugDataSize: Integer): Boolean; overload;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
- BinDebug: TJclBinDebugGenerator; out LinkerBugUnit: string;
- out MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean; overload;
- // Source Locations
- type
- TJclDebugInfoSource = class;
- PJclLocationInfo = ^TJclLocationInfo;
- TJclLocationInfo = record
- Address: Pointer; // Error address
- UnitName: string; // Name of Delphi unit
- ProcedureName: string; // Procedure name
- OffsetFromProcName: Integer; // Offset from Address to ProcedureName symbol location
- LineNumber: Integer; // Line number
- OffsetFromLineNumber: Integer; // Offset from Address to LineNumber symbol location
- SourceName: string; // Module file name
- DebugInfo: TJclDebugInfoSource; // Location object
- BinaryFileName: string; // Name of the binary file containing the symbol
- end;
- TJclLocationInfoExValues = set of (lievLocationInfo, lievProcedureStartLocationInfo, lievUnitVersionInfo);
- TJclCustomLocationInfoList = class;
- TJclLocationInfoListOptions = set of (liloAutoGetAddressInfo, liloAutoGetLocationInfo, liloAutoGetUnitVersionInfo);
- TJclLocationInfoEx = class(TPersistent)
- private
- FAddress: Pointer;
- FBinaryFileName: string;
- FDebugInfo: TJclDebugInfoSource;
- FLineNumber: Integer;
- FLineNumberOffsetFromProcedureStart: Integer;
- FModuleName: string;
- FOffsetFromLineNumber: Integer;
- FOffsetFromProcName: Integer;
- FParent: TJclCustomLocationInfoList;
- FProcedureName: string;
- FSourceName: string;
- FSourceUnitName: string;
- FUnitVersionDateTime: TDateTime;
- FUnitVersionExtra: string;
- FUnitVersionLogPath: string;
- FUnitVersionRCSfile: string;
- FUnitVersionRevision: string;
- FVAddress: Pointer;
- FValues: TJclLocationInfoExValues;
- procedure Fill(AOptions: TJclLocationInfoListOptions);
- function GetAsString: string;
- protected
- procedure AssignTo(Dest: TPersistent); override;
- public
- constructor Create(AParent: TJclCustomLocationInfoList; Address: Pointer);
- procedure Clear; virtual;
- property Address: Pointer read FAddress write FAddress;
- property AsString: string read GetAsString;
- property BinaryFileName: string read FBinaryFileName write FBinaryFileName;
- property DebugInfo: TJclDebugInfoSource read FDebugInfo write FDebugInfo;
- property LineNumber: Integer read FLineNumber write FLineNumber;
- property LineNumberOffsetFromProcedureStart: Integer read FLineNumberOffsetFromProcedureStart write FLineNumberOffsetFromProcedureStart;
- property ModuleName: string read FModuleName write FModuleName;
- property OffsetFromLineNumber: Integer read FOffsetFromLineNumber write FOffsetFromLineNumber;
- property OffsetFromProcName: Integer read FOffsetFromProcName write FOffsetFromProcName;
- property ProcedureName: string read FProcedureName write FProcedureName;
- property SourceName: string read FSourceName write FSourceName;
- { this is equal to TJclLocationInfo.UnitName, but has been renamed because
- UnitName is a class function in TObject since Delphi 2009 }
- property SourceUnitName: string read FSourceUnitName write FSourceUnitName;
- property UnitVersionDateTime: TDateTime read FUnitVersionDateTime write FUnitVersionDateTime;
- property UnitVersionExtra: string read FUnitVersionExtra write FUnitVersionExtra;
- property UnitVersionLogPath: string read FUnitVersionLogPath write FUnitVersionLogPath;
- property UnitVersionRCSfile: string read FUnitVersionRCSfile write FUnitVersionRCSfile;
- property UnitVersionRevision: string read FUnitVersionRevision write FUnitVersionRevision;
- property VAddress: Pointer read FVAddress write FVAddress;
- property Values: TJclLocationInfoExValues read FValues write FValues;
- end;
- TJclLocationInfoClass = class of TJclLocationInfoEx;
- TJclCustomLocationInfoListClass = class of TJclCustomLocationInfoList;
- TJclCustomLocationInfoList = class(TPersistent)
- protected
- FItemClass: TJclLocationInfoClass;
- FItems: TObjectList;
- FOptions: TJclLocationInfoListOptions;
- function GetAsString: string;
- function GetCount: Integer;
- function InternalAdd(Addr: Pointer): TJclLocationInfoEx;
- protected
- procedure AssignTo(Dest: TPersistent); override;
- public
- constructor Create; virtual;
- destructor Destroy; override;
- procedure AddStackInfoList(AStackInfoList: TObject);
- procedure Clear;
- property AsString: string read GetAsString;
- property Count: Integer read GetCount;
- property Options: TJclLocationInfoListOptions read FOptions write FOptions;
- end;
- TJclLocationInfoList = class(TJclCustomLocationInfoList)
- private
- function GetItems(AIndex: Integer): TJclLocationInfoEx;
- public
- constructor Create; override;
- function Add(Addr: Pointer): TJclLocationInfoEx;
- property Items[AIndex: Integer]: TJclLocationInfoEx read GetItems; default;
- end;
- TJclDebugInfoSource = class(TObject)
- private
- FModule: HMODULE;
- FModuleCodeSize: SizeInt;
- function GetFileName: TFileName;
- protected
- function VAFromAddr(const Addr: Pointer): DWORD; virtual;
- function AddrFromVA(const VA: DWORD): Pointer; virtual;
- public
- constructor Create(AModule: HMODULE); virtual;
- function InitializeSource: Boolean; virtual; abstract;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; virtual; abstract;
- function GetAddress(const UnitName, ProcName: string): Pointer; virtual; abstract;
- property Module: HMODULE read FModule;
- property FileName: TFileName read GetFileName;
- property ModuleCodeSize: SizeInt read FModuleCodeSize;
- end;
- TJclDebugInfoSourceClass = class of TJclDebugInfoSource;
- TJclDebugInfoList = class(TObjectList)
- private
- function GetItemFromModule(const Module: HMODULE): TJclDebugInfoSource;
- function GetItems(Index: TJclListSize): TJclDebugInfoSource;
- protected
- function CreateDebugInfo(const Module: HMODULE): TJclDebugInfoSource;
- public
- class procedure RegisterDebugInfoSource(
- const InfoSourceClass: TJclDebugInfoSourceClass);
- class procedure UnRegisterDebugInfoSource(
- const InfoSourceClass: TJclDebugInfoSourceClass);
- class procedure RegisterDebugInfoSourceFirst(
- const InfoSourceClass: TJclDebugInfoSourceClass);
- class procedure NeedInfoSourceClassList;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- property ItemFromModule[const Module: HMODULE]: TJclDebugInfoSource read GetItemFromModule;
- property Items[Index: TJclListSize]: TJclDebugInfoSource read GetItems;
- end;
- // Various source location implementations
- TJclDebugInfoMap = class(TJclDebugInfoSource)
- private
- FScanner: TJclMapScanner;
- public
- destructor Destroy; override;
- function InitializeSource: Boolean; override;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; override;
- function GetAddress(const UnitName, ProcName: string): Pointer; override;
- end;
- TJclDebugInfoBinary = class(TJclDebugInfoSource)
- private
- FScanner: TJclBinDebugScanner;
- FStream: TCustomMemoryStream;
- public
- destructor Destroy; override;
- function InitializeSource: Boolean; override;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; override;
- function GetAddress(const UnitName, ProcName: string): Pointer; override;
- end;
- TJclDebugInfoExports = class(TJclDebugInfoSource)
- private
- {$IFDEF BORLAND}
- FImage: TJclPeBorImage;
- {$ENDIF BORLAND}
- {$IFDEF FPC}
- FImage: TJclPeImage;
- {$ENDIF FPC}
- function IsAddressInThisExportedFunction(Addr: PByteArray; FunctionStartAddr: TJclAddr): Boolean;
- public
- destructor Destroy; override;
- function InitializeSource: Boolean; override;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; override;
- function GetAddress(const UnitName, ProcName: string): Pointer; override;
- end;
- {$IFDEF BORLAND}
- {$IFNDEF WINSCP}
- TJclDebugInfoTD32 = class(TJclDebugInfoSource)
- private
- FImage: TJclPeBorTD32Image;
- public
- destructor Destroy; override;
- function InitializeSource: Boolean; override;
- procedure GenerateUnmangledNames;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; override;
- function GetAddress(const UnitName, ProcName: string): Pointer; override;
- end;
- {$ENDIF ~WINSCP}
- {$ENDIF BORLAND}
- TJclDebugInfoSymbols = class(TJclDebugInfoSource)
- public
- class function LoadDebugFunctions: Boolean;
- class function UnloadDebugFunctions: Boolean;
- class function InitializeDebugSymbols: Boolean;
- class function CleanupDebugSymbols: Boolean;
- function InitializeSource: Boolean; override;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; override;
- function GetAddress(const UnitName, ProcName: string): Pointer; override;
- end;
- // Source location functions
- function Caller(Level: Integer = 0; FastStackWalk: Boolean = False): Pointer;
- procedure BeginGetLocationInfoCache;
- procedure EndGetLocationInfoCache;
- function GetLocationInfo(const Addr: Pointer): TJclLocationInfo; overload;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean; overload;
- function GetLocationInfoStr(const Addr: Pointer; IncludeModuleName: Boolean = False;
- IncludeAddressOffset: Boolean = False; IncludeStartProcLineOffset: Boolean = False;
- IncludeVAddress: Boolean = False): string;
- function DebugInfoAvailable(const Module: HMODULE): Boolean;
- procedure ClearLocationData;
- function FileByLevel(const Level: Integer = 0): string;
- function ModuleByLevel(const Level: Integer = 0): string;
- function ProcByLevel(const Level: Integer = 0; OnlyProcedureName: boolean =false): string;
- function LineByLevel(const Level: Integer = 0): Integer;
- function MapByLevel(const Level: Integer; var File_, Module_, Proc_: string; var Line_: Integer): Boolean;
- function FileOfAddr(const Addr: Pointer): string;
- function ModuleOfAddr(const Addr: Pointer): string;
- function ProcOfAddr(const Addr: Pointer): string;
- function LineOfAddr(const Addr: Pointer): Integer;
- function MapOfAddr(const Addr: Pointer; var File_, Module_, Proc_: string; var Line_: Integer): Boolean;
- function ExtractClassName(const ProcedureName: string): string;
- function ExtractMethodName(const ProcedureName: string): string;
- // Original function names, deprecated will be removed in V2.0; do not use!
- function __FILE__(const Level: Integer = 0): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __MODULE__(const Level: Integer = 0): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __PROC__(const Level: Integer = 0): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __LINE__(const Level: Integer = 0): Integer; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __MAP__(const Level: Integer; var _File, _Module, _Proc: string; var _Line: Integer): Boolean; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __FILE_OF_ADDR__(const Addr: Pointer): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __MODULE_OF_ADDR__(const Addr: Pointer): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __PROC_OF_ADDR__(const Addr: Pointer): string; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __LINE_OF_ADDR__(const Addr: Pointer): Integer; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- function __MAP_OF_ADDR__(const Addr: Pointer; var _File, _Module, _Proc: string;
- var _Line: Integer): Boolean; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF}
- // Stack info routines base list
- type
- TJclStackBaseList = class(TObjectList)
- private
- FThreadID: DWORD;
- FTimeStamp: TDateTime;
- protected
- FOnDestroy: TNotifyEvent;
- public
- constructor Create;
- destructor Destroy; override;
- property ThreadID: DWORD read FThreadID;
- property TimeStamp: TDateTime read FTimeStamp;
- end;
- // Stack info routines
- type
- PDWORD_PTRArray = ^TDWORD_PTRArray;
- TDWORD_PTRArray = array [0..(MaxInt - $F) div SizeOf(DWORD_PTR)] of DWORD_PTR;
- {$IFNDEF FPC}
- PDWORD_PTR = ^DWORD_PTR;
- {$ENDIF ~FPC}
- PStackFrame = ^TStackFrame;
- TStackFrame = record
- CallerFrame: TJclAddr;
- CallerAddr: TJclAddr;
- end;
- PStackInfo = ^TStackInfo;
- TStackInfo = record
- CallerAddr: TJclAddr;
- Level: Integer;
- CallerFrame: TJclAddr;
- DumpSize: DWORD;
- ParamSize: DWORD;
- ParamPtr: PDWORD_PTRArray;
- case Integer of
- 0:
- (StackFrame: PStackFrame);
- 1:
- (DumpPtr: PJclByteArray);
- end;
- TJclStackInfoItem = class(TObject)
- private
- FStackInfo: TStackInfo;
- function GetCallerAddr: Pointer;
- function GetLogicalAddress: TJclAddr;
- public
- property CallerAddr: Pointer read GetCallerAddr;
- property LogicalAddress: TJclAddr read GetLogicalAddress;
- property StackInfo: TStackInfo read FStackInfo;
- end;
- TJclStackInfoList = class(TJclStackBaseList)
- private
- FIgnoreLevels: Integer;
- TopOfStack: TJclAddr;
- BaseOfStack: TJclAddr;
- FStackData: PPointer;
- FFramePointer: Pointer;
- FModuleInfoList: TJclModuleInfoList;
- FCorrectOnAccess: Boolean;
- FSkipFirstItem: Boolean;
- FDelayedTrace: Boolean;
- FInStackTracing: Boolean;
- FRaw: Boolean;
- FStackOffset: Int64;
- {$IFDEF CPU64}
- procedure CaptureBackTrace;
- {$ENDIF CPU64}
- function GetItems(Index: TJclListSize): TJclStackInfoItem;
- function NextStackFrame(var StackFrame: PStackFrame; var StackInfo: TStackInfo): Boolean;
- procedure StoreToList(const StackInfo: TStackInfo);
- procedure TraceStackFrames;
- procedure TraceStackRaw;
- {$IFDEF CPU32}
- procedure DelayStoreStack;
- {$ENDIF CPU32}
- function ValidCallSite(CodeAddr: TJclAddr; out CallInstructionSize: Cardinal): Boolean;
- function ValidStackAddr(StackAddr: TJclAddr): Boolean;
- function GetCount: Integer;
- procedure CorrectOnAccess(ASkipFirstItem: Boolean);
- public
- constructor Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer); overload;
- constructor Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer; ADelayedTrace: Boolean); overload;
- constructor Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack: Pointer); overload;
- constructor Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack, ATopOfStack: Pointer); overload;
- destructor Destroy; override;
- procedure ForceStackTracing;
- procedure AddToStrings(Strings: TStrings; IncludeModuleName: Boolean = False;
- IncludeAddressOffset: Boolean = False; IncludeStartProcLineOffset: Boolean = False;
- IncludeVAddress: Boolean = False);
- property DelayedTrace: Boolean read FDelayedTrace;
- property Items[Index: TJclListSize]: TJclStackInfoItem read GetItems; default;
- property IgnoreLevels: Integer read FIgnoreLevels;
- property Count: Integer read GetCount;
- property Raw: Boolean read FRaw;
- end;
- {$IFDEF WINSCP}
- procedure DoExceptionStackTrace(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean;
- BaseOfStack: Pointer);
- procedure DoExceptFrameTrace;
- {$ENDIF}
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer): TJclStackInfoList; overload;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer;
- DelayedTrace: Boolean): TJclStackInfoList; overload;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer;
- DelayedTrace: Boolean; BaseOfStack: Pointer): TJclStackInfoList; overload;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer;
- DelayedTrace: Boolean; BaseOfStack, TopOfStack: Pointer): TJclStackInfoList; overload;
- function JclCreateThreadStackTrace(Raw: Boolean; const ThreadHandle: THandle): TJclStackInfoList;
- function JclCreateThreadStackTraceFromID(Raw: Boolean; ThreadID: DWORD): TJclStackInfoList;
- function JclLastExceptStackList: TJclStackInfoList;
- function JclLastExceptStackListToStrings(Strings: TStrings; IncludeModuleName: Boolean = False;
- IncludeAddressOffset: Boolean = False; IncludeStartProcLineOffset: Boolean = False;
- IncludeVAddress: Boolean = False): Boolean;
- function JclGetExceptStackList(ThreadID: DWORD): TJclStackInfoList;
- function JclGetExceptStackListToStrings(ThreadID: DWORD; Strings: TStrings;
- IncludeModuleName: Boolean = False; IncludeAddressOffset: Boolean = False;
- IncludeStartProcLineOffset: Boolean = False; IncludeVAddress: Boolean = False): Boolean;
- // helper function for DUnit runtime memory leak check
- procedure JclClearGlobalStackData;
- // Exception frame info routines
- type
- PJmpInstruction = ^TJmpInstruction;
- TJmpInstruction = packed record // from System.pas
- OpCode: Byte;
- Distance: Longint;
- end;
- TExcDescEntry = record // from System.pas
- VTable: Pointer;
- Handler: Pointer;
- end;
- PExcDesc = ^TExcDesc;
- TExcDesc = packed record // from System.pas
- JMP: TJmpInstruction;
- case Integer of
- 0:
- (Instructions: array [0..0] of Byte);
- 1:
- (Cnt: Integer;
- ExcTab: array [0..0] of TExcDescEntry);
- end;
- PExcFrame = ^TExcFrame;
- TExcFrame = record // from System.pas
- Next: PExcFrame;
- Desc: PExcDesc;
- FramePointer: Pointer;
- case Integer of
- 0:
- ();
- 1:
- (ConstructedObject: Pointer);
- 2:
- (SelfOfMethod: Pointer);
- end;
- PJmpTable = ^TJmpTable;
- TJmpTable = packed record
- OPCode: Word; // FF 25 = JMP DWORD PTR [$xxxxxxxx], encoded as $25FF
- Ptr: Pointer;
- end;
- TExceptFrameKind =
- (efkUnknown, efkFinally, efkAnyException, efkOnException, efkAutoException);
- TJclExceptFrame = class(TObject)
- private
- FFrameKind: TExceptFrameKind;
- FFrameLocation: Pointer;
- FCodeLocation: Pointer;
- FExcTab: array of TExcDescEntry;
- protected
- procedure AnalyseExceptFrame(AExcDesc: PExcDesc);
- public
- constructor Create(AFrameLocation: Pointer; AExcDesc: PExcDesc);
- function Handles(ExceptObj: TObject): Boolean;
- function HandlerInfo(ExceptObj: TObject; out HandlerAt: Pointer): Boolean;
- property CodeLocation: Pointer read FCodeLocation;
- property FrameLocation: Pointer read FFrameLocation;
- property FrameKind: TExceptFrameKind read FFrameKind;
- end;
- TJclExceptFrameList = class(TJclStackBaseList)
- private
- FIgnoreLevels: Integer;
- function GetItems(Index: TJclListSize): TJclExceptFrame;
- protected
- function AddFrame(AFrame: PExcFrame): TJclExceptFrame;
- public
- constructor Create(AIgnoreLevels: Integer);
- procedure TraceExceptionFrames;
- property Items[Index: TJclListSize]: TJclExceptFrame read GetItems;
- property IgnoreLevels: Integer read FIgnoreLevels write FIgnoreLevels;
- end;
- function JclCreateExceptFrameList(AIgnoreLevels: Integer): TJclExceptFrameList;
- function JclLastExceptFrameList: TJclExceptFrameList;
- function JclGetExceptFrameList(ThreadID: DWORD): TJclExceptFrameList;
- function JclStartExceptionTracking: Boolean;
- function JclStopExceptionTracking: Boolean;
- function JclExceptionTrackingActive: Boolean;
- function JclTrackExceptionsFromLibraries: Boolean;
- // Thread exception tracking support
- type
- TJclDebugThread = class(TThread)
- private
- FSyncException: TObject;
- FThreadName: string;
- procedure DoHandleException;
- function GetThreadInfo: string;
- protected
- procedure DoNotify;
- procedure DoSyncHandleException; dynamic;
- procedure HandleException(Sender: TObject = nil);
- public
- constructor Create(ASuspended: Boolean; const AThreadName: string = '');
- destructor Destroy; override;
- property SyncException: TObject read FSyncException;
- property ThreadInfo: string read GetThreadInfo;
- property ThreadName: string read FThreadName;
- end;
- TJclDebugThreadNotifyEvent = procedure(Thread: TJclDebugThread) of object;
- TJclThreadIDNotifyEvent = procedure(ThreadID: DWORD) of object;
- TJclDebugThreadList = class(TObject)
- private
- FList: TObjectList;
- FLock: TJclCriticalSection;
- FReadLock: TJclCriticalSection;
- FRegSyncThreadID: DWORD;
- FSaveCreationStack: Boolean;
- FUnregSyncThreadID: DWORD;
- FOnSyncException: TJclDebugThreadNotifyEvent;
- FOnThreadRegistered: TJclThreadIDNotifyEvent;
- FOnThreadUnregistered: TJclThreadIDNotifyEvent;
- function GetThreadClassNames(ThreadID: DWORD): string;
- function GetThreadInfos(ThreadID: DWORD): string;
- function GetThreadNames(ThreadID: DWORD): string;
- procedure DoSyncThreadRegistered;
- procedure DoSyncThreadUnregistered;
- function GetThreadCreationTime(ThreadID: DWORD): TDateTime;
- function GetThreadHandle(Index: Integer): THandle;
- function GetThreadID(Index: Integer): DWORD;
- function GetThreadIDCount: Integer;
- function GetThreadParentID(ThreadID: DWORD): DWORD;
- function GetThreadValues(ThreadID: DWORD; Index: Integer): string;
- function IndexOfThreadID(ThreadID: DWORD): Integer;
- protected
- procedure DoSyncException(Thread: TJclDebugThread);
- procedure DoThreadRegistered(Thread: TThread);
- procedure DoThreadUnregistered(Thread: TThread);
- procedure InternalRegisterThread(Thread: TThread; ThreadID: DWORD; const ThreadName: string);
- procedure InternalUnregisterThread(Thread: TThread; ThreadID: DWORD);
- public
- constructor Create;
- destructor Destroy; override;
- function AddStackListToLocationInfoList(ThreadID: DWORD; AList: TJclLocationInfoList): Boolean;
- procedure RegisterThread(Thread: TThread; const ThreadName: string);
- procedure RegisterThreadID(AThreadID: DWORD; const ThreadName: string = '');
- procedure UnregisterThread(Thread: TThread);
- procedure UnregisterThreadID(AThreadID: DWORD);
- property Lock: TJclCriticalSection read FLock;
- //property ThreadClassNames[ThreadID: DWORD]: string index 1 read GetThreadValues;
- property SaveCreationStack: Boolean read FSaveCreationStack write FSaveCreationStack;
- property ThreadClassNames[ThreadID: DWORD]: string read GetThreadClassNames;
- property ThreadCreationTime[ThreadID: DWORD]: TDateTime read GetThreadCreationTime;
- property ThreadHandles[Index: Integer]: THandle read GetThreadHandle;
- property ThreadIDs[Index: Integer]: DWORD read GetThreadID;
- property ThreadIDCount: Integer read GetThreadIDCount;
- //property ThreadInfos[ThreadID: DWORD]: string index 2 read GetThreadValues;
- property ThreadInfos[ThreadID: DWORD]: string read GetThreadInfos;
- //property ThreadNames[ThreadID: DWORD]: string index 0 read GetThreadValues;
- property ThreadNames[ThreadID: DWORD]: string read GetThreadNames;
- property ThreadParentIDs[ThreadID: DWORD]: DWORD read GetThreadParentID;
- property OnSyncException: TJclDebugThreadNotifyEvent read FOnSyncException write FOnSyncException;
- property OnThreadRegistered: TJclThreadIDNotifyEvent read FOnThreadRegistered write FOnThreadRegistered;
- property OnThreadUnregistered: TJclThreadIDNotifyEvent read FOnThreadUnregistered write FOnThreadUnregistered;
- end;
- TJclDebugThreadInfo = class(TObject)
- private
- FCreationTime: TDateTime;
- FParentThreadID: DWORD;
- FStackList: TJclStackInfoList;
- FThreadClassName: string;
- FThreadID: DWORD;
- FThreadHandle: THandle;
- FThreadName: string;
- public
- constructor Create(AParentThreadID, AThreadID: DWORD; AStack: Boolean);
- destructor Destroy; override;
- property CreationTime: TDateTime read FCreationTime;
- property ParentThreadID: DWORD read FParentThreadID;
- property StackList: TJclStackInfoList read FStackList;
- property ThreadClassName: string read FThreadClassName write FThreadClassName;
- property ThreadID: DWORD read FThreadID;
- property ThreadHandle: THandle read FThreadHandle write FThreadHandle;
- property ThreadName: string read FThreadName write FThreadName;
- end;
- TJclThreadInfoOptions = set of (tioIsMainThread, tioName, tioCreationTime, tioParentThreadID, tioStack, tioCreationStack);
- TJclCustomThreadInfo = class(TPersistent)
- protected
- FCreationTime: TDateTime;
- FCreationStack: TJclCustomLocationInfoList;
- FName: string;
- FParentThreadID: DWORD;
- FStack: TJclCustomLocationInfoList;
- FThreadID: DWORD;
- FValues: TJclThreadInfoOptions;
- procedure AssignTo(Dest: TPersistent); override;
- function GetStackClass: TJclCustomLocationInfoListClass; virtual;
- public
- constructor Create;
- destructor Destroy; override;
- property CreationTime: TDateTime read FCreationTime write FCreationTime;
- property Name: string read FName write FName;
- property ParentThreadID: DWORD read FParentThreadID write FParentThreadID;
- property ThreadID: DWORD read FThreadID write FThreadID;
- property Values: TJclThreadInfoOptions read FValues write FValues;
- end;
- TJclThreadInfo = class(TJclCustomThreadInfo)
- private
- function GetAsString: string;
- procedure InternalFill(AThreadHandle: THandle; AThreadID: DWORD; AGatherOptions: TJclThreadInfoOptions; AExceptThread: Boolean);
- function GetStack(const AIndex: Integer): TJclLocationInfoList;
- protected
- function GetStackClass: TJclCustomLocationInfoListClass; override;
- public
- procedure Fill(AThreadHandle: THandle; AThreadID: DWORD; AGatherOptions: TJclThreadInfoOptions);
- procedure FillFromExceptThread(AGatherOptions: TJclThreadInfoOptions);
- property AsString: string read GetAsString;
- property CreationStack: TJclLocationInfoList index 1 read GetStack;
- property Stack: TJclLocationInfoList index 2 read GetStack;
- end;
- TJclThreadInfoList = class(TPersistent)
- private
- FGatherOptions: TJclThreadInfoOptions;
- FItems: TObjectList;
- function GetAsString: string;
- function GetCount: Integer;
- function GetItems(AIndex: Integer): TJclThreadInfo;
- procedure InternalGather(AIncludeThreadIDs, AExcludeThreadIDs: array of DWORD);
- protected
- procedure AssignTo(Dest: TPersistent); override;
- public
- constructor Create;
- destructor Destroy; override;
- function Add: TJclThreadInfo;
- procedure Clear;
- procedure Gather(AExceptThreadID: DWORD);
- procedure GatherExclude(AThreadIDs: array of DWORD);
- procedure GatherInclude(AThreadIDs: array of DWORD);
- property AsString: string read GetAsString;
- property Count: Integer read GetCount;
- property GatherOptions: TJclThreadInfoOptions read FGatherOptions write FGatherOptions;
- property Items[AIndex: Integer]: TJclThreadInfo read GetItems; default;
- end;
- function JclDebugThreadList: TJclDebugThreadList;
- function JclHookThreads: Boolean;
- function JclUnhookThreads: Boolean;
- function JclThreadsHooked: Boolean;
- // Miscellanuous
- {$IFDEF MSWINDOWS}
- {$IFNDEF WINSCP}
- function EnableCrashOnCtrlScroll(const Enable: Boolean): Boolean;
- {$ENDIF ~WINSCP}
- function IsDebuggerAttached: Boolean;
- function IsHandleValid(Handle: THandle): Boolean;
- {$ENDIF MSWINDOWS}
- {$IFDEF SUPPORTS_EXTSYM}
- {$EXTERNALSYM __FILE__}
- {$EXTERNALSYM __LINE__}
- {$ENDIF SUPPORTS_EXTSYM}
- const
- EnvironmentVarNtSymbolPath = '_NT_SYMBOL_PATH'; // do not localize
- EnvironmentVarAlternateNtSymbolPath = '_NT_ALTERNATE_SYMBOL_PATH'; // do not localize
- MaxStackTraceItems = 4096;
- // JCL binary debug data generator and scanner
- const
- JclDbgDataSignature = $4742444A; // JDBG
- JclDbgDataResName = AnsiString('JCLDEBUG'); // do not localize
- JclDbgHeaderVersion = 1; // JCL 1.11 and 1.20
- JclDbgFileExtension = '.jdbg'; // do not localize
- JclMapFileExtension = '.map'; // do not localize
- DrcFileExtension = '.drc'; // do not localize
- // Global exceptional stack tracker enable routines and variables
- type
- TJclStackTrackingOption =
- (stStack, stExceptFrame, stRawMode, stAllModules, stStaticModuleList,
- stDelayedTrace, stTraceAllExceptions, stMainThreadOnly, stDisableIfDebuggerAttached
- {$IFDEF HAS_EXCEPTION_STACKTRACE}
- // Resolves the Exception.Stacktrace string when the exception is raised. This is more
- // exact if modules are unloaded before the delayed resolving happens, but it slows down
- // the exception handling if no stacktrace is needed for the exception.
- , stImmediateExceptionStacktraceResolving
- {$ENDIF HAS_EXCEPTION_STACKTRACE}
- // stCleanRawStack does a deeper analysis of the callstack by evaluating the instructions
- // that manipulate the stack.
- // It removes many cases of false positives but may also remove valid entries if it runs
- // into a function that does non-standard stack pointer manipulation.
- , stCleanRawStack // experimental
- );
- TJclStackTrackingOptions = set of TJclStackTrackingOption;
- {$IFDEF HAS_EXCEPTION_STACKTRACE}
- TJclExceptionStacktraceOption = (
- estoIncludeModuleName,
- estoIncludeAdressOffset,
- estoIncludeStartProcLineOffset,
- estoIncludeVAddress
- );
- TJclExceptionStacktraceOptions = set of TJclExceptionStacktraceOption;
- {$ENDIF HAS_EXCEPTION_STACKTRACE}
- var
- JclStackTrackingOptions: TJclStackTrackingOptions = [stStack];
- {$IFDEF HAS_EXCEPTION_STACKTRACE}
- // JclExceptionStacktraceOptions controls the Exception.Stacktrace string's format
- JclExceptionStacktraceOptions: TJclExceptionStacktraceOptions =
- [estoIncludeModuleName, estoIncludeAdressOffset, estoIncludeStartProcLineOffset, estoIncludeVAddress];
- {$ENDIF HAS_EXCEPTION_STACKTRACE}
- { JclDebugInfoSymbolPaths specifies a list of paths, separated by ';', in
- which the DebugInfoSymbol scanner should look for symbol information. }
- JclDebugInfoSymbolPaths: string = '';
- // functions to add/remove exception classes to be ignored if StTraceAllExceptions is not set
- procedure AddIgnoredException(const ExceptionClass: TClass);
- procedure AddIgnoredExceptionByName(const AExceptionClassName: string);
- procedure RemoveIgnoredException(const ExceptionClass: TClass);
- procedure RemoveIgnoredExceptionByName(const AExceptionClassName: string);
- function IsIgnoredException(const ExceptionClass: TClass): Boolean;
- // function to add additional system modules to be included in the stack trace
- procedure AddModule(const ModuleName: string);
- {$IFDEF UNITVERSIONING}
- const
- UnitVersioning: TUnitVersionInfo = (
- RCSfile: '$URL$';
- Revision: '$Revision$';
- Date: '$Date$';
- LogPath: 'JCL\source\windows';
- Extra: '';
- Data: nil
- );
- {$ENDIF UNITVERSIONING}
- implementation
- uses
- {$IFDEF HAS_UNITSCOPE}
- System.RTLConsts,
- System.Types, // for inlining TList.Remove
- {$IFDEF HAS_UNIT_CHARACTER}
- System.Character,
- {$ENDIF HAS_UNIT_CHARACTER}
- {$IFDEF SUPPORTS_GENERICS}
- System.Generics.Collections,
- {$ENDIF SUPPORTS_GENERICS}
- {$ELSE ~HAS_UNITSCOPE}
- RTLConsts,
- {$IFDEF HAS_UNIT_CHARACTER}
- Character,
- {$ENDIF HAS_UNIT_CHARACTER}
- {$IFDEF SUPPORTS_GENERICS}
- Generics.Collections,
- {$ENDIF SUPPORTS_GENERICS}
- {$ENDIF ~HAS_UNITSCOPE}
- {$IFDEF MSWINDOWS}
- {$IFNDEF WINSCP}
- JclRegistry,
- {$ELSE}
- System.AnsiStrings,
- {$ENDIF ~WINSCP}
- {$ENDIF MSWINDOWS}
- JclHookExcept, JclAnsiStrings, JclStrings, JclSysInfo, JclSysUtils, JclWin32,
- {$IFNDEF WINSCP}JclStringConversions,{$ENDIF ~WINSCP} JclResources;
- //=== Helper assembler routines ==============================================
- const
- ModuleCodeOffset = $1000;
- var
- HexMap: array[AnsiChar] of Byte;
- JclDebugFinalized: Boolean;
- GlobalStackListLiveCount: Integer;
- procedure FreeJclDebugGlobals;
- forward;
- {$STACKFRAMES OFF}
- function GetFramePointer: Pointer;
- asm
- {$IFDEF CPU32}
- MOV EAX, EBP
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- MOV RAX, RBP
- {$ENDIF CPU64}
- end;
- function GetStackPointer: Pointer;
- asm
- {$IFDEF CPU32}
- MOV EAX, ESP
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- MOV RAX, RSP
- {$ENDIF CPU64}
- end;
- {$IFDEF CPU32}
- function GetExceptionPointer: Pointer;
- asm
- XOR EAX, EAX
- MOV EAX, FS:[EAX]
- end;
- {$ENDIF CPU32}
- // Reference: Matt Pietrek, MSJ, Under the hood, on TIBs:
- // http://www.microsoft.com/MSJ/archive/S2CE.HTM
- function GetStackTop: TJclAddr;
- asm
- {$IFDEF CPU32}
- MOV EAX, FS:[0].NT_TIB32.StackBase
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- {$IFDEF DELPHI64_TEMPORARY}
- //TODO: check if the FS version doesn't work in general in 64-bit mode
- MOV RAX, GS:[ABS 8]
- {$ELSE ~DELPHI64_TEMPORARY}
- MOV RAX, FS:[0].NT_TIB64.StackBase
- {$ENDIF ~DELPHI64_TEMPORARY}
- {$ENDIF CPU64}
- end;
- {$IFDEF STACKFRAMES_ON}
- {$STACKFRAMES ON}
- {$ENDIF STACKFRAMES_ON}
- //=== Diagnostics ===========================================================
- procedure AssertKindOf(const ClassName: string; const Obj: TObject);
- var
- C: TClass;
- begin
- if not Obj.ClassNameIs(ClassName) then
- begin
- C := Obj.ClassParent;
- while (C <> nil) and (not C.ClassNameIs(ClassName)) do
- C := C.ClassParent;
- Assert(C <> nil);
- end;
- end;
- procedure AssertKindOf(const ClassType: TClass; const Obj: TObject);
- begin
- Assert(Obj.InheritsFrom(ClassType));
- end;
- procedure TraceMsg(const Msg: string);
- begin
- OutputDebugString(PChar(StrDoubleQuote(Msg)));
- end;
- {$IFNDEF WINSCP}
- procedure TraceFmt(const Fmt: string; const Args: array of const);
- begin
- OutputDebugString(PChar(Format(StrDoubleQuote(Fmt), Args)));
- end;
- {$ENDIF}
- procedure TraceLoc(const Msg: string);
- begin
- OutputDebugString(PChar(Format('%s:%u (%s) "%s"',
- [FileByLevel(1), LineByLevel(1), ProcByLevel(1), Msg])));
- end;
- procedure TraceLocFmt(const Fmt: string; const Args: array of const);
- var
- S: string;
- begin
- S := Format('%s:%u (%s) ', [FileByLevel(1), LineByLevel(1), ProcByLevel(1)]) +
- Format(StrDoubleQuote(Fmt), Args);
- OutputDebugString(PChar(S));
- end;
- //=== { TJclModuleInfoList } =================================================
- constructor TJclModuleInfoList.Create(ADynamicBuild, ASystemModulesOnly: Boolean);
- begin
- inherited Create(True);
- FDynamicBuild := ADynamicBuild;
- FSystemModulesOnly := ASystemModulesOnly;
- if not FDynamicBuild then
- BuildModulesList;
- end;
- function TJclModuleInfoList.AddModule(Module: HMODULE; SystemModule: Boolean): Boolean;
- begin
- Result := not IsValidModuleAddress(Pointer(Module)) and
- (CreateItemForAddress(Pointer(Module), SystemModule) <> nil);
- end;
- {function SortByStartAddress(Item1, Item2: Pointer): Integer;
- begin
- Result := INT_PTR(TJclModuleInfo(Item2).StartAddr) - INT_PTR(TJclModuleInfo(Item1).StartAddr);
- end;}
- procedure TJclModuleInfoList.BuildModulesList;
- var
- List: TStringList;
- I: Integer;
- CurModule: PLibModule;
- begin
- if FSystemModulesOnly then
- begin
- CurModule := LibModuleList;
- while CurModule <> nil do
- begin
- CreateItemForAddress(Pointer(CurModule.Instance), True);
- CurModule := CurModule.Next;
- end;
- end
- else
- begin
- List := TStringList.Create;
- try
- LoadedModulesList(List, GetCurrentProcessId, True);
- for I := 0 to List.Count - 1 do
- CreateItemForAddress(List.Objects[I], False);
- finally
- List.Free;
- end;
- end;
- //Sort(SortByStartAddress);
- end;
- function TJclModuleInfoList.CreateItemForAddress(Addr: Pointer; SystemModule: Boolean): TJclModuleInfo;
- var
- Module: HMODULE;
- ModuleSize: DWORD;
- begin
- Result := nil;
- Module := ModuleFromAddr(Addr);
- if Module > 0 then
- begin
- ModuleSize := PeMapImgSize(Pointer(Module));
- if ModuleSize <> 0 then
- begin
- Result := TJclModuleInfo.Create;
- Result.FStartAddr := Pointer(Module);
- Result.FSize := ModuleSize;
- Result.FEndAddr := Pointer(Module + ModuleSize - 1);
- if SystemModule then
- Result.FSystemModule := True
- else
- Result.FSystemModule := IsSystemModule(Module);
- end;
- end;
- if Result <> nil then
- Add(Result);
- end;
- function TJclModuleInfoList.GetItems(Index: TJclListSize): TJclModuleInfo;
- begin
- Result := TJclModuleInfo(Get(Index));
- end;
- function TJclModuleInfoList.GetModuleFromAddress(Addr: Pointer): TJclModuleInfo;
- var
- I: Integer;
- Item: TJclModuleInfo;
- begin
- Result := nil;
- for I := 0 to Count - 1 do
- begin
- Item := Items[I];
- if (TJclAddr(Item.StartAddr) <= TJclAddr(Addr)) and (TJclAddr(Item.EndAddr) > TJclAddr(Addr)) then
- begin
- Result := Item;
- Break;
- end;
- end;
- if DynamicBuild and (Result = nil) then
- Result := CreateItemForAddress(Addr, False);
- end;
- function TJclModuleInfoList.IsSystemModuleAddress(Addr: Pointer): Boolean;
- var
- Item: TJclModuleInfo;
- begin
- Item := ModuleFromAddress[Addr];
- Result := (Item <> nil) and Item.SystemModule;
- end;
- function TJclModuleInfoList.IsValidModuleAddress(Addr: Pointer): Boolean;
- begin
- Result := ModuleFromAddress[Addr] <> nil;
- end;
- //=== { TJclAbstractMapParser } ==============================================
- constructor TJclAbstractMapParser.Create(const MapFileName: TFileName; Module: HMODULE);
- begin
- inherited Create;
- FModule := Module;
- if FileExists(MapFileName) then
- FStream := TJclFileMappingStream.Create(MapFileName, fmOpenRead or fmShareDenyWrite);
- end;
- constructor TJclAbstractMapParser.Create(const MapFileName: TFileName);
- begin
- Create(MapFileName, 0);
- end;
- destructor TJclAbstractMapParser.Destroy;
- begin
- FreeAndNil(FStream);
- inherited Destroy;
- end;
- function TJclAbstractMapParser.GetLinkerBugUnitName: string;
- begin
- Result := MapStringToStr(FLinkerBugUnitName);
- end;
- class function TJclAbstractMapParser.MapStringToFileName(MapString: PJclMapString): string;
- var
- PEnd: PJclMapString;
- begin
- if MapString = nil then
- begin
- Result := '';
- Exit;
- end;
- PEnd := MapString;
- while (PEnd^ <> #0) and not (PEnd^ in ['=', #10, #13]) do
- Inc(PEnd);
- if (PEnd^ = '=') then
- begin
- while (PEnd >= MapString) and (PEnd^ <> ' ') do
- Dec(PEnd);
- while (PEnd >= MapString) and ((PEnd-1)^ = ' ') do
- Dec(PEnd);
- end;
- SetString(Result, MapString, PEnd - MapString);
- end;
- class function TJclAbstractMapParser.MapStringToModuleName(MapString: PJclMapString): string;
- var
- PStart, PEnd, PExtension: PJclMapString;
- begin
- if MapString = nil then
- begin
- Result := '';
- Exit;
- end;
- PEnd := MapString;
- while (PEnd^ <> #0) and not (PEnd^ in ['=', #10, #13]) do
- Inc(PEnd);
- if (PEnd^ = '=') then
- begin
- while (PEnd >= MapString) and (PEnd^ <> ' ') do
- Dec(PEnd);
- while (PEnd >= MapString) and ((PEnd-1)^ = ' ') do
- Dec(PEnd);
- end;
- PExtension := PEnd;
- while (PExtension >= MapString) and (PExtension^ <> '.') and (PExtension^ <> '|') do
- Dec(PExtension);
- if (StrLICompA(PExtension, '.pas ', 5) = 0) or
- (StrLICompA(PExtension, '.obj ', 5) = 0) then
- PEnd := PExtension;
- PExtension := PEnd;
- while (PExtension >= MapString) and (PExtension^ <> '|') and (PExtension^ <> '\') do
- Dec(PExtension);
- if PExtension >= MapString then
- PStart := PExtension + 1
- else
- PStart := MapString;
- SetString(Result, PStart, PEnd - PStart);
- end;
- class function TJclAbstractMapParser.MapStringToStr(MapString: PJclMapString;
- IgnoreSpaces: Boolean): string;
- var
- P: PJclMapString;
- begin
- if MapString = nil then
- begin
- Result := '';
- Exit;
- end;
- if MapString^ = '(' then
- begin
- Inc(MapString);
- P := MapString;
- while (P^ <> #0) and not (P^ in [')', #10, #13]) do
- Inc(P);
- end
- else
- begin
- P := MapString;
- if IgnoreSpaces then
- while (P^ <> #0) and not (P^ in ['(', #10, #13]) do
- Inc(P)
- else
- while (P^ <> #0) and (P^ <> '(') and (P^ > ' ') do
- Inc(P);
- end;
- SetString(Result, MapString, P - MapString);
- end;
- function IsDecDigit(P: PJclMapString): Boolean; {$IFDEF SUPPORTS_INLINE}inline;{$ENDIF}
- begin
- Result := False;
- case P^ of
- '0'..'9':
- Result := True;
- end;
- end;
- function SkipMapBlock(P, EndPos: PJclMapString): PJclMapString;
- begin
- Result := P;
- while Result < EndPos do
- begin
- if not IsDecDigit(Result) then
- Break;
- Inc(Result);
- // Skip to the end of the line
- while Result < EndPos do
- begin
- case Result^ of
- #10, #13:
- Break;
- end;
- Inc(Result);
- end;
- // Skip WhiteSpaces
- while (Result < EndPos) and (Result^ <= ' ') do
- Inc(Result);
- end;
- end;
- function AnsiStrPosIdxLen(const SubStr, S: PAnsiChar; Len: Integer): Integer;
- var
- I: Integer;
- SubStrLen: Integer;
- FirstCh: AnsiChar;
- begin
- Result := 0;
- if Len = 0 then
- Exit;
- I := 0;
- FirstCh := SubStr[0];
- if FirstCh = #0 then
- Exit;
- SubStrLen := StrLenA(SubStr);
- while I < Len do
- begin
- while (I < Len) and (S[I] <> FirstCh) do
- Inc(I);
- if I = Len then
- Break;
- if StrLCompA(SubStr, @S[I], SubStrLen) = 0 then
- begin
- Result := I + 1;
- Exit;
- end;
- Inc(I);
- end;
- end;
- procedure TJclAbstractMapParser.Parse;
- const
- TableHeader : array [0..3] of PJclMapString = ('Start', 'Length', 'Name', 'Class');
- SegmentsHeader : array [0..3] of PJclMapString = ('Detailed', 'map', 'of', 'segments');
- PublicsByNameHeader : array [0..3] of PJclMapString = ('Address', 'Publics', 'by', 'Name');
- PublicsByValueHeader : array [0..3] of PJclMapString = ('Address', 'Publics', 'by', 'Value');
- LineNumbersPrefix : PJclMapString = 'Line numbers for';
- var
- CurrPos, EndPos: PJclMapString;
- {$IFNDEF COMPILER9_UP}
- PreviousA,
- {$ENDIF COMPILER9_UP}
- A: TJclMapAddress;
- L: Integer;
- P1, P2: PJclMapString;
- function Eof: Boolean;
- begin
- Result := CurrPos >= EndPos;
- end;
- function SkipWhiteSpace: PJclMapString;
- var
- LEndPos: PJclMapString;
- begin
- Result := CurrPos;
- LEndPos := EndPos;
- while (Result < LEndPos) and (Result^ <= ' ') do
- Inc(Result);
- CurrPos := Result;
- end;
- procedure SkipEndLine;
- var
- P, LEndPos: PJclMapString;
- begin
- P := CurrPos;
- LEndPos := EndPos;
- while P < LEndPos do
- begin
- case P^ of
- #10, #13:
- Break;
- end;
- Inc(P);
- end;
- // Skip WhiteSpaces
- while (P < LEndPos) and (P^ <= ' ') do
- Inc(P);
- CurrPos := P;
- end;
- function ReadTrimmedTextLine(var Len: Integer): PJclMapString;
- var
- Start, P: PJclMapString;
- begin
- Start := CurrPos;
- P := Start;
- while (P^ <> #0) and not (P^ in [#10, #13]) do
- Inc(P);
- CurrPos := P;
- // Trim
- while (Start < P) and (Start^ <> #0) and (Start^ <= ' ') do
- Inc(Start);
- Dec(P);
- while (P > Start) and (P^ <= ' ') do
- Dec(P);
- Inc(P);
- Result := Start;
- Len := P - Start;
- if Len < 0 then
- Len := 0;
- end;
- function ReadDecValue: Integer;
- var
- P: PJclMapString;
- begin
- P := CurrPos;
- Result := 0;
- while P^ in ['0'..'9'] do
- begin
- Result := Result * 10 + (Ord(P^) - Ord('0'));
- Inc(P);
- end;
- CurrPos := P;
- end;
- function ReadHexValue: DWORD;
- var
- C: AnsiChar;
- V: Byte;
- P: PJclMapString;
- begin
- P := CurrPos;
- Result := 0;
- repeat
- C := P^;
- V := HexMap[C];
- if V and $80 <> 0 then
- Break;
- Result := (Result shl 4) or V;
- Inc(P);
- until False;
- if (C = 'H') or (C = 'h') then
- Inc(P);
- CurrPos := P;
- end;
- procedure ReadAddress(var Result: TJclMapAddress);
- begin
- Result.Segment := ReadHexValue;
- if CurrPos^ = ':' then
- begin
- Inc(CurrPos);
- Result.Offset := ReadHexValue;
- end
- else
- Result.Offset := 0;
- end;
- function ReadString: PJclMapString;
- var
- P, LEndPos: PJclMapString;
- begin
- // Skip WhiteSpaces
- LEndPos := EndPos;
- P := CurrPos;
- while (P < LEndPos) and (P^ <= ' ') do
- Inc(P);
- Result := P;
- while {(P^ <> #0) and} (P^ > ' ') do
- Inc(P);
- CurrPos := P;
- end;
- procedure FindParam(Param: AnsiChar);
- var
- P: PJclMapString;
- begin
- P := CurrPos;
- while not ((P^ = Param) and (P[1] = '=')) do
- Inc(P);
- CurrPos := P + 2;
- end;
- function SyncToHeader(const Header: array of PJclMapString): Boolean;
- var
- S: PJclMapString;
- SLen: Integer;
- TokenIndex, OldPosition, CurrentPosition: Integer;
- begin
- Result := False;
- while not Eof do
- begin
- S := ReadTrimmedTextLine(SLen);
- if SLen > 0 then
- begin
- TokenIndex := Low(Header);
- CurrentPosition := 0;
- OldPosition := 0;
- while (TokenIndex <= High(Header)) do
- begin
- CurrentPosition := AnsiStrPosIdxLen(Header[TokenIndex], S, SLen);
- if (CurrentPosition <= OldPosition) then
- begin
- CurrentPosition := 0;
- Break;
- end;
- OldPosition := CurrentPosition;
- Inc(TokenIndex);
- end;
- Result := CurrentPosition <> 0;
- if Result then
- Break;
- end;
- SkipEndLine;
- end;
- if not Eof then
- SkipWhiteSpace;
- end;
- function SyncToPrefix(const Prefix: PJclMapString): Boolean;
- var
- P: PJclMapString;
- PrefixLen: Integer;
- begin
- if Eof then
- begin
- Result := False;
- Exit;
- end;
- SkipWhiteSpace;
- P := CurrPos;
- PrefixLen := StrLenA(Prefix);
- Result := StrLCompA(Prefix, P, PrefixLen) = 0;
- if Result then
- CurrPos := P + PrefixLen;
- SkipWhiteSpace;
- end;
- begin
- if FStream <> nil then
- begin
- FLinkerBug := False;
- {$IFNDEF COMPILER9_UP}
- PreviousA.Segment := 0;
- PreviousA.Offset := 0;
- {$ENDIF COMPILER9_UP}
- CurrPos := FStream.Memory;
- EndPos := CurrPos + FStream.Size;
- if SyncToHeader(TableHeader) then
- while IsDecDigit(CurrPos) do
- begin
- ReadAddress(A);
- SkipWhiteSpace;
- L := ReadHexValue;
- P1 := ReadString;
- P2 := ReadString;
- SkipEndLine;
- ClassTableItem(A, L, P1, P2);
- end;
- if SyncToHeader(SegmentsHeader) then
- while IsDecDigit(CurrPos) do
- begin
- ReadAddress(A);
- SkipWhiteSpace;
- L := ReadHexValue;
- FindParam('C');
- P1 := ReadString;
- FindParam('M');
- P2 := ReadString;
- SkipEndLine;
- SegmentItem(A, L, P1, P2);
- end;
- if SyncToHeader(PublicsByNameHeader) then
- begin
- if not CanHandlePublicsByName then
- CurrPos := SkipMapBlock(CurrPos, EndPos)
- else
- begin
- while IsDecDigit(CurrPos) do
- begin
- ReadAddress(A);
- P1 := ReadString;
- SkipEndLine; // compatibility with C++Builder MAP files
- PublicsByNameItem(A, P1);
- end;
- end;
- end;
- if SyncToHeader(PublicsByValueHeader) then
- if not CanHandlePublicsByValue then
- CurrPos := SkipMapBlock(CurrPos, EndPos)
- else
- begin
- while not Eof and IsDecDigit(CurrPos) do
- begin
- ReadAddress(A);
- P1 := ReadString;
- SkipEndLine; // compatibility with C++Builder MAP files
- PublicsByValueItem(A, P1);
- end;
- end;
- while SyncToPrefix(LineNumbersPrefix) do
- begin
- FLastUnitName := CurrPos;
- FLastUnitFileName := CurrPos;
- while FLastUnitFileName^ <> '(' do
- Inc(FLastUnitFileName);
- SkipEndLine;
- LineNumberUnitItem(FLastUnitName, FLastUnitFileName);
- repeat
- SkipWhiteSpace;
- L := ReadDecValue;
- SkipWhiteSpace;
- ReadAddress(A);
- SkipWhiteSpace;
- LineNumbersItem(L, A);
- {$IFNDEF COMPILER9_UP}
- if not FLinkerBug and (A.Offset < PreviousA.Offset) then
- begin
- FLinkerBugUnitName := FLastUnitName;
- FLinkerBug := True;
- end;
- PreviousA := A;
- {$ENDIF COMPILER9_UP}
- until not IsDecDigit(CurrPos);
- end;
- end;
- end;
- //=== { TJclMapParser } ======================================================
- procedure TJclMapParser.ClassTableItem(const Address: TJclMapAddress;
- Len: Integer; SectionName, GroupName: PJclMapString);
- begin
- if Assigned(FOnClassTable) then
- FOnClassTable(Self, Address, Len, MapStringToStr(SectionName), MapStringToStr(GroupName));
- end;
- procedure TJclMapParser.LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress);
- begin
- if Assigned(FOnLineNumbers) then
- FOnLineNumbers(Self, LineNumber, Address);
- end;
- procedure TJclMapParser.LineNumberUnitItem(UnitName, UnitFileName: PJclMapString);
- begin
- if Assigned(FOnLineNumberUnit) then
- FOnLineNumberUnit(Self, MapStringToStr(UnitName), MapStringToStr(UnitFileName));
- end;
- function TJclMapParser.CanHandlePublicsByName: Boolean;
- begin
- Result := Assigned(FOnPublicsByName);
- end;
- function TJclMapParser.CanHandlePublicsByValue: Boolean;
- begin
- Result := Assigned(FOnPublicsByValue);
- end;
- procedure TJclMapParser.PublicsByNameItem(const Address: TJclMapAddress;
- Name: PJclMapString);
- begin
- if Assigned(FOnPublicsByName) then
- // MAP files generated by C++Builder have spaces in their identifier names
- FOnPublicsByName(Self, Address, MapStringToStr(Name, True));
- end;
- procedure TJclMapParser.PublicsByValueItem(const Address: TJclMapAddress;
- Name: PJclMapString);
- begin
- if Assigned(FOnPublicsByValue) then
- // MAP files generated by C++Builder have spaces in their identifier names
- FOnPublicsByValue(Self, Address, MapStringToStr(Name, True));
- end;
- procedure TJclMapParser.SegmentItem(const Address: TJclMapAddress;
- Len: Integer; GroupName, UnitName: PJclMapString);
- begin
- if Assigned(FOnSegmentItem) then
- FOnSegmentItem(Self, Address, Len, MapStringToStr(GroupName), MapStringToModuleName(UnitName));
- end;
- //=== { TJclMapScanner } =====================================================
- constructor TJclMapScanner.Create(const MapFileName: TFileName; Module: HMODULE);
- begin
- inherited Create(MapFileName, Module);
- Scan;
- end;
- function TJclMapScanner.MAPAddrToVA(const Addr: DWORD): DWORD;
- begin
- // MAP file format was changed in Delphi 2005
- // before Delphi 2005: segments started at offset 0
- // only one segment of code
- // after Delphi 2005: segments started at code base address (module base address + $10000)
- // 2 segments of code
- if (Length(FSegmentClasses) > 0) and (FSegmentClasses[0].Start > 0) and (Addr >= FSegmentClasses[0].Start) then
- // Delphi 2005 and later
- // The first segment should be code starting at module base address + $10000
- Result := Addr - FSegmentClasses[0].Start
- else
- // before Delphi 2005
- Result := Addr;
- end;
- class function TJclMapScanner.MapStringCacheToFileName(
- var MapString: TJclMapStringCache): string;
- begin
- Result := MapString.CachedValue;
- if Result = '' then
- begin
- Result := MapStringToFileName(MapString.RawValue);
- MapString.CachedValue := Result;
- end;
- end;
- class function TJclMapScanner.MapStringCacheToModuleName(
- var MapString: TJclMapStringCache): string;
- begin
- Result := MapString.CachedValue;
- if Result = '' then
- begin
- Result := MapStringToModuleName(MapString.RawValue);
- MapString.CachedValue := Result;
- end;
- end;
- class function TJclMapScanner.MapStringCacheToStr(var MapString: TJclMapStringCache;
- IgnoreSpaces: Boolean): string;
- begin
- Result := MapString.CachedValue;
- if Result = '' then
- begin
- Result := MapStringToStr(MapString.RawValue, IgnoreSpaces);
- MapString.CachedValue := Result;
- end;
- end;
- procedure TJclMapScanner.ClassTableItem(const Address: TJclMapAddress; Len: Integer;
- SectionName, GroupName: PJclMapString);
- var
- C: Integer;
- SectionHeader: PImageSectionHeader;
- begin
- C := Length(FSegmentClasses);
- SetLength(FSegmentClasses, C + 1);
- FSegmentClasses[C].Segment := Address.Segment;
- FSegmentClasses[C].Start := Address.Offset;
- FSegmentClasses[C].Addr := Address.Offset; // will be fixed below while considering module mapped address
- // test GroupName because SectionName = '.tls' in Delphi and '_tls' in BCB
- if StrLICompA(GroupName, 'TLS', 3) = 0 then
- begin
- FSegmentClasses[C].VA := FSegmentClasses[C].Start;
- FSegmentClasses[C].GroupName.TLS := True;
- end
- else
- begin
- FSegmentClasses[C].VA := MAPAddrToVA(FSegmentClasses[C].Start);
- FSegmentClasses[C].GroupName.TLS := False;
- end;
- FSegmentClasses[C].Len := Len;
- FSegmentClasses[C].SectionName.RawValue := SectionName;
- FSegmentClasses[C].GroupName.RawValue := GroupName;
- if FModule <> 0 then
- begin
- { Fix the section addresses }
- SectionHeader := PeMapImgFindSectionFromModule(Pointer(FModule), MapStringToStr(SectionName));
- if SectionHeader = nil then
- { before Delphi 2005 the class names where used for the section names }
- SectionHeader := PeMapImgFindSectionFromModule(Pointer(FModule), MapStringToStr(GroupName));
- if SectionHeader <> nil then
- begin
- FSegmentClasses[C].Addr := TJclAddr(FModule) + SectionHeader.VirtualAddress;
- FSegmentClasses[C].VA := SectionHeader.VirtualAddress;
- end;
- end;
- end;
- function TJclMapScanner.LineNumberFromAddr(Addr: DWORD): Integer;
- var
- Dummy: Integer;
- begin
- Result := LineNumberFromAddr(Addr, Dummy);
- end;
- function Search_MapLineNumber(Item1, Item2: Pointer): Integer;
- begin
- Result := Integer(PJclMapLineNumber(Item1)^.VA) - PInteger(Item2)^;
- end;
- function TJclMapScanner.LineNumberFromAddr(Addr: DWORD; out Offset: Integer): Integer;
- var
- I: Integer;
- ModuleStartAddr: DWORD;
- begin
- ModuleStartAddr := ModuleStartFromAddr(Addr);
- Result := 0;
- Offset := 0;
- I := SearchDynArray(FLineNumbers, SizeOf(FLineNumbers[0]), Search_MapLineNumber, @Addr, True);
- if (I <> -1) and (FLineNumbers[I].VA >= ModuleStartAddr) then
- begin
- Result := FLineNumbers[I].LineNumber;
- Offset := Addr - FLineNumbers[I].VA;
- end;
- end;
- procedure TJclMapScanner.LineNumbersItem(LineNumber: Integer; const Address: TJclMapAddress);
- var
- SegIndex, C: Integer;
- VA: DWORD;
- Added: Boolean;
- begin
- Added := False;
- for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
- if (FSegmentClasses[SegIndex].Segment = Address.Segment)
- and (DWORD(Address.Offset) < FSegmentClasses[SegIndex].Len) then
- begin
- if FSegmentClasses[SegIndex].GroupName.TLS then
- Va := Address.Offset
- else
- VA := MAPAddrToVA(Address.Offset + FSegmentClasses[SegIndex].Start);
- { Starting with Delphi 2005, "empty" units are listes with the last line and
- the VA 0001:00000000. When we would accept 0 VAs here, System.pas functions
- could be mapped to other units and line numbers. Discaring such items should
- have no impact on the correct information, because there can't be a function
- that starts at VA 0. }
- if VA = 0 then
- Continue;
- if FLineNumbersCnt = Length(FLineNumbers) then
- begin
- if FLineNumbersCnt < 512 then
- SetLength(FLineNumbers, FLineNumbersCnt + 512)
- else
- SetLength(FLineNumbers, FLineNumbersCnt * 2);
- end;
- FLineNumbers[FLineNumbersCnt].Segment := FSegmentClasses[SegIndex].Segment;
- FLineNumbers[FLineNumbersCnt].VA := VA;
- FLineNumbers[FLineNumbersCnt].LineNumber := LineNumber;
- FLineNumbers[FLineNumbersCnt].UnitName := FCurrentUnitName;
- Inc(FLineNumbersCnt);
- Added := True;
- if FNewUnitFileName <> nil then
- begin
- C := Length(FSourceNames);
- SetLength(FSourceNames, C + 1);
- FSourceNames[C].Segment := FSegmentClasses[SegIndex].Segment;
- FSourceNames[C].VA := VA;
- FSourceNames[C].ProcName.RawValue := FNewUnitFileName;
- FNewUnitFileName := nil;
- end;
- Break;
- end;
- if not Added then
- Inc(FLineNumberErrors);
- end;
- procedure TJclMapScanner.LineNumberUnitItem(UnitName, UnitFileName: PJclMapString);
- begin
- FNewUnitFileName := UnitFileName;
- FCurrentUnitName := UnitName;
- end;
- function TJclMapScanner.GetLineNumberByIndex(Index: Integer): TJCLMapLineNumber;
- begin
- Result := FLineNumbers[Index];
- end;
- function TJclMapScanner.IndexOfSegment(Addr: DWORD): Integer;
- var
- L, R: Integer;
- S: PJclMapSegment;
- begin
- R := Length(FSegments) - 1;
- Result := FLastAccessedSegementIndex;
- if Result <= R then
- begin
- S := @FSegments[Result];
- if (S.StartVA <= Addr) and (Addr < S.EndVA) then
- Exit;
- end;
- // binary search
- L := 0;
- while L <= R do
- begin
- Result := L + (R - L) div 2;
- S := @FSegments[Result];
- if Addr >= S.EndVA then
- L := Result + 1
- else
- begin
- R := Result - 1;
- if (S.StartVA <= Addr) and (Addr < S.EndVA) then
- begin
- FLastAccessedSegementIndex := Result;
- Exit;
- end;
- end;
- end;
- Result := -1;
- end;
- function TJclMapScanner.ModuleNameFromAddr(Addr: DWORD): string;
- var
- I: Integer;
- begin
- I := IndexOfSegment(Addr);
- if I <> -1 then
- Result := MapStringCacheToModuleName(FSegments[I].UnitName)
- else
- Result := '';
- end;
- function TJclMapScanner.ModuleStartFromAddr(Addr: DWORD): DWORD;
- var
- I: Integer;
- begin
- I := IndexOfSegment(Addr);
- Result := DWORD(-1);
- if I <> -1 then
- Result := FSegments[I].StartVA;
- end;
- function TJclMapScanner.ProcNameFromAddr(Addr: DWORD): string;
- var
- Dummy: Integer;
- begin
- Result := ProcNameFromAddr(Addr, Dummy);
- end;
- function Search_MapProcName(Item1, Item2: Pointer): Integer;
- begin
- Result := Integer(PJclMapProcName(Item1)^.VA) - PInteger(Item2)^;
- end;
- function TJclMapScanner.ProcNameFromAddr(Addr: DWORD; out Offset: Integer): string;
- var
- I: Integer;
- ModuleStartAddr: DWORD;
- begin
- ModuleStartAddr := ModuleStartFromAddr(Addr);
- Result := '';
- Offset := 0;
- I := SearchDynArray(FProcNames, SizeOf(FProcNames[0]), Search_MapProcName, @Addr, True);
- if (I <> -1) and (FProcNames[I].VA >= ModuleStartAddr) then
- begin
- Result := MapStringCacheToStr(FProcNames[I].ProcName, True);
- Offset := Addr - FProcNames[I].VA;
- end;
- end;
- function TJclMapScanner.CanHandlePublicsByName: Boolean;
- begin
- Result := False;
- end;
- function TJclMapScanner.CanHandlePublicsByValue: Boolean;
- begin
- Result := True;
- end;
- procedure TJclMapScanner.PublicsByNameItem(const Address: TJclMapAddress; Name: PJclMapString);
- begin
- end;
- procedure TJclMapScanner.PublicsByValueItem(const Address: TJclMapAddress; Name: PJclMapString);
- var
- SegIndex: Integer;
- begin
- for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
- if (FSegmentClasses[SegIndex].Segment = Address.Segment)
- and (DWORD(Address.Offset) < FSegmentClasses[SegIndex].Len) then
- begin
- if FProcNamesCnt = Length(FProcNames) then
- begin
- if FProcNamesCnt < 512 then
- SetLength(FProcNames, FProcNamesCnt + 512)
- else
- SetLength(FProcNames, FProcNamesCnt * 2);
- end;
- FProcNames[FProcNamesCnt].Segment := FSegmentClasses[SegIndex].Segment;
- if FSegmentClasses[SegIndex].GroupName.TLS then
- FProcNames[FProcNamesCnt].VA := Address.Offset
- else
- FProcNames[FProcNamesCnt].VA := MAPAddrToVA(Address.Offset + FSegmentClasses[SegIndex].Start);
- FProcNames[FProcNamesCnt].ProcName.RawValue := Name;
- Inc(FProcNamesCnt);
- Break;
- end;
- end;
- {function Sort_MapLineNumber(Item1, Item2: Pointer): Integer;
- begin
- Result := Integer(PJclMapLineNumber(Item1)^.VA) - Integer(PJclMapLineNumber(Item2)^.VA);
- end;}
- function Sort_MapProcName(Item1, Item2: Pointer): Integer;
- begin
- Result := Integer(PJclMapProcName(Item1)^.VA) - Integer(PJclMapProcName(Item2)^.VA);
- end;
- function Sort_MapSegment(Item1, Item2: Pointer): Integer;
- begin
- Result := Integer(PJclMapSegment(Item1)^.EndVA) - Integer(PJclMapSegment(Item2)^.EndVA);
- if Result = 0 then
- Result := Integer(PJclMapSegment(Item1)^.StartVA) - Integer(PJclMapSegment(Item2)^.StartVA);
- end;
- type
- PJclMapLineNumberArray = ^TJclMapLineNumberArray;
- TJclMapLineNumberArray = array[0..MaxInt div SizeOf(TJclMapLineNumber) - 1] of TJclMapLineNumber;
- PJclMapProcNameArray = ^TJclMapProcNameArray;
- TJclMapProcNameArray = array[0..MaxInt div SizeOf(TJclMapProcName) - 1] of TJclMapProcName;
- // specialized quicksort functions
- procedure SortLineNumbers(ArrayVar: PJclMapLineNumberArray; L, R: Integer);
- var
- I, J, P: Integer;
- Temp: TJclMapLineNumber;
- AV: PJclMapLineNumber;
- V: Integer;
- begin
- repeat
- I := L;
- J := R;
- P := (L + R) shr 1;
- repeat
- V := Integer(ArrayVar[P].VA);
- AV := @ArrayVar[I];
- while Integer(AV.VA) - V < 0 do begin Inc(I); Inc(AV); end;
- AV := @ArrayVar[J];
- while Integer(AV.VA) - V > 0 do begin Dec(J); Dec(AV); end;
- if I <= J then
- begin
- if I <> J then
- begin
- Temp := ArrayVar[I];
- ArrayVar[I] := ArrayVar[J];
- ArrayVar[J] := Temp;
- end;
- if P = I then
- P := J
- else if P = J then
- P := I;
- Inc(I);
- Dec(J);
- end;
- until I > J;
- if L < J then
- SortLineNumbers(ArrayVar, L, J);
- L := I;
- until I >= R;
- end;
- procedure SortProcNames(ArrayVar: PJclMapProcNameArray; L, R: Integer);
- var
- I, J, P: Integer;
- Temp: TJclMapProcName;
- V: Integer;
- AV: PJclMapProcName;
- begin
- repeat
- I := L;
- J := R;
- P := (L + R) shr 1;
- repeat
- V := Integer(ArrayVar[P].VA);
- AV := @ArrayVar[I];
- while Integer(AV.VA) - V < 0 do begin Inc(I); Inc(AV); end;
- AV := @ArrayVar[J];
- while Integer(AV.VA) - V > 0 do begin Dec(J); Dec(AV); end;
- if I <= J then
- begin
- if I <> J then
- begin
- Temp := ArrayVar[I];
- ArrayVar[I] := ArrayVar[J];
- ArrayVar[J] := Temp;
- end;
- if P = I then
- P := J
- else if P = J then
- P := I;
- Inc(I);
- Dec(J);
- end;
- until I > J;
- if L < J then
- SortProcNames(ArrayVar, L, J);
- L := I;
- until I >= R;
- end;
- procedure TJclMapScanner.Scan;
- begin
- FLineNumberErrors := 0;
- FSegmentCnt := 0;
- FProcNamesCnt := 0;
- FLastAccessedSegementIndex := 0;
- Parse;
- SetLength(FLineNumbers, FLineNumbersCnt);
- SetLength(FProcNames, FProcNamesCnt);
- SetLength(FSegments, FSegmentCnt);
- //SortDynArray(FLineNumbers, SizeOf(FLineNumbers[0]), Sort_MapLineNumber);
- if FLineNumbers <> nil then
- SortLineNumbers(PJclMapLineNumberArray(FLineNumbers), 0, Length(FLineNumbers) - 1);
- //SortDynArray(FProcNames, SizeOf(FProcNames[0]), Sort_MapProcName);
- if FProcNames <> nil then
- SortProcNames(PJclMapProcNameArray(FProcNames), 0, Length(FProcNames) - 1);
- SortDynArray(FSegments, SizeOf(FSegments[0]), Sort_MapSegment);
- SortDynArray(FSourceNames, SizeOf(FSourceNames[0]), Sort_MapProcName);
- end;
- procedure TJclMapScanner.SegmentItem(const Address: TJclMapAddress; Len: Integer;
- GroupName, UnitName: PJclMapString);
- var
- SegIndex: Integer;
- VA: DWORD;
- begin
- for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
- if (FSegmentClasses[SegIndex].Segment = Address.Segment)
- and (DWORD(Address.Offset) < FSegmentClasses[SegIndex].Len) then
- begin
- if FSegmentClasses[SegIndex].GroupName.TLS then
- VA := Address.Offset
- else
- VA := MAPAddrToVA(Address.Offset + FSegmentClasses[SegIndex].Start);
- if FSegmentCnt mod 16 = 0 then
- SetLength(FSegments, FSegmentCnt + 16);
- FSegments[FSegmentCnt].Segment := FSegmentClasses[SegIndex].Segment;
- FSegments[FSegmentCnt].StartVA := VA;
- FSegments[FSegmentCnt].EndVA := VA + DWORD(Len);
- FSegments[FSegmentCnt].UnitName.RawValue := UnitName;
- Inc(FSegmentCnt);
- Break;
- end;
- end;
- function TJclMapScanner.SourceNameFromAddr(Addr: DWORD): string;
- var
- I: Integer;
- ModuleStartVA: DWORD;
- begin
- // try with line numbers first (Delphi compliance)
- ModuleStartVA := ModuleStartFromAddr(Addr);
- Result := '';
- I := SearchDynArray(FSourceNames, SizeOf(FSourceNames[0]), Search_MapProcName, @Addr, True);
- if (I <> -1) and (FSourceNames[I].VA >= ModuleStartVA) then
- Result := MapStringCacheToStr(FSourceNames[I].ProcName);
- if Result = '' then
- begin
- // try with module names (C++Builder compliance)
- I := IndexOfSegment(Addr);
- if I <> -1 then
- Result := MapStringCacheToFileName(FSegments[I].UnitName);
- end;
- end;
- function TJclMapScanner.VAFromUnitAndProcName(const UnitName, ProcName: string): DWORD;
- var
- I: Integer;
- QualifiedName: string;
- begin
- Result := 0;
- if (UnitName = '') or (ProcName = '') then
- Exit;
- QualifiedName := UnitName + '.' + ProcName;
- for I := Low(FProcNames) to High(FProcNames) do
- begin
- if CompareText(MapStringCacheToStr(FProcNames[I].ProcName, True), QualifiedName) = 0 then
- begin
- Result := FProcNames[i].VA;
- Break;
- end;
- end;
- end;
- // JCL binary debug format string encoding/decoding routines
- { Strings are compressed to following 6bit format (A..D represents characters) and terminated with }
- { 6bit #0 char. First char = #1 indicates non compressed text, #2 indicates compressed text with }
- { leading '@' character }
- { }
- { 7 6 5 4 3 2 1 0 | }
- {--------------------------------- }
- { B1 B0 A5 A4 A3 A2 A1 A0 | Data byte 0 }
- {--------------------------------- }
- { C3 C2 C1 C0 B5 B4 B3 B2 | Data byte 1 }
- {--------------------------------- }
- { D5 D4 D3 D2 D1 D0 C5 C4 | Data byte 2 }
- {--------------------------------- }
- function SimpleCryptString(const S: TUTF8String): TUTF8String;
- var
- I: Integer;
- C: Byte;
- P: PByte;
- begin
- SetLength(Result, Length(S));
- P := PByte(Result);
- for I := 1 to Length(S) do
- begin
- C := Ord(S[I]);
- if C <> $AA then
- C := C xor $AA;
- P^ := C;
- Inc(P);
- end;
- end;
- function DecodeNameString(const S: PAnsiChar): string;
- var
- I, B: Integer;
- C: Byte;
- P: PByte;
- Buffer: array [0..255] of AnsiChar;
- begin
- Result := '';
- B := 0;
- P := PByte(S);
- case P^ of
- 1:
- begin
- Inc(P);
- Result := UTF8ToString(SimpleCryptString(PAnsiChar(P)));
- Exit;
- end;
- 2:
- begin
- Inc(P);
- Buffer[B] := '@';
- Inc(B);
- end;
- end;
- I := 0;
- C := 0;
- repeat
- case I and $03 of
- 0:
- C := P^ and $3F;
- 1:
- begin
- C := (P^ shr 6) and $03;
- Inc(P);
- Inc(C, (P^ and $0F) shl 2);
- end;
- 2:
- begin
- C := (P^ shr 4) and $0F;
- Inc(P);
- Inc(C, (P^ and $03) shl 4);
- end;
- 3:
- begin
- C := (P^ shr 2) and $3F;
- Inc(P);
- end;
- end;
- case C of
- $00:
- Break;
- $01..$0A:
- Inc(C, Ord('0') - $01);
- $0B..$24:
- Inc(C, Ord('A') - $0B);
- $25..$3E:
- Inc(C, Ord('a') - $25);
- $3F:
- C := Ord('_');
- end;
- Buffer[B] := AnsiChar(C);
- Inc(B);
- Inc(I);
- until B >= SizeOf(Buffer) - 1;
- Buffer[B] := #0;
- Result := UTF8ToString(Buffer);
- end;
- function EncodeNameString(const S: string): AnsiString;
- var
- I, StartIndex, EndIndex: Integer;
- C: Byte;
- P: PByte;
- begin
- if (Length(S) > 1) and (S[1] = '@') then
- StartIndex := 1
- else
- StartIndex := 0;
- for I := StartIndex + 1 to Length(S) do
- if not CharIsValidIdentifierLetter(Char(S[I])) then
- begin
- {$IFDEF SUPPORTS_UNICODE}
- Result := #1 + SimpleCryptString(UTF8Encode(S)) + #0; // UTF8Encode is much faster than StringToUTF8
- {$ELSE}
- Result := #1 + SimpleCryptString(StringToUTF8(S)) + #0;
- {$ENDIF SUPPORTS_UNICODE}
- Exit;
- end;
- SetLength(Result, Length(S) + StartIndex);
- P := Pointer(Result);
- if StartIndex = 1 then
- P^ := 2 // store '@' leading char information
- else
- Dec(P);
- EndIndex := Length(S) - StartIndex;
- for I := 0 to EndIndex do // including null char
- begin
- if I = EndIndex then
- C := 0
- else
- C := Byte(S[I + 1 + StartIndex]);
- case AnsiChar(C) of
- #0:
- C := 0;
- '0'..'9':
- Dec(C, Ord('0') - $01);
- 'A'..'Z':
- Dec(C, Ord('A') - $0B);
- 'a'..'z':
- Dec(C, Ord('a') - $25);
- '_':
- C := $3F;
- else
- C := $3F;
- end;
- case I and $03 of
- 0:
- begin
- Inc(P);
- P^ := C;
- end;
- 1:
- begin
- P^ := P^ or (C and $03) shl 6;
- Inc(P);
- P^ := (C shr 2) and $0F;
- end;
- 2:
- begin
- P^ := P^ or Byte(C shl 4);
- Inc(P);
- P^ := (C shr 4) and $03;
- end;
- 3:
- P^ := P^ or (C shl 2);
- end;
- end;
- SetLength(Result, TJclAddr(P) - TJclAddr(Pointer(Result)) + 1);
- end;
- function ConvertMapFileToJdbgFile(const MapFileName: TFileName): Boolean;
- var
- Dummy1: string;
- Dummy2, Dummy3, Dummy4: Integer;
- begin
- Result := ConvertMapFileToJdbgFile(MapFileName, Dummy1, Dummy2, Dummy3, Dummy4);
- end;
- function ConvertMapFileToJdbgFile(const MapFileName: TFileName; out LinkerBugUnit: string;
- out LineNumberErrors: Integer): Boolean;
- var
- Dummy1, Dummy2: Integer;
- begin
- Result := ConvertMapFileToJdbgFile(MapFileName, LinkerBugUnit, LineNumberErrors,
- Dummy1, Dummy2);
- end;
- function ConvertMapFileToJdbgFile(const MapFileName: TFileName; out LinkerBugUnit: string;
- out LineNumberErrors, MapFileSize, JdbgFileSize: Integer): Boolean;
- var
- JDbgFileName: TFileName;
- Generator: TJclBinDebugGenerator;
- begin
- JDbgFileName := ChangeFileExt(MapFileName, JclDbgFileExtension);
- Generator := TJclBinDebugGenerator.Create(MapFileName, 0);
- try
- MapFileSize := Generator.Stream.Size;
- JdbgFileSize := Generator.DataStream.Size;
- Result := (Generator.DataStream.Size > 0) and Generator.CalculateCheckSum;
- if Result then
- Generator.DataStream.SaveToFile(JDbgFileName);
- LinkerBugUnit := Generator.LinkerBugUnitName;
- LineNumberErrors := Generator.LineNumberErrors;
- finally
- Generator.Free;
- end;
- end;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName, MapFileName: TFileName;
- out LinkerBugUnit: string; out MapFileSize, JclDebugDataSize: Integer): Boolean;
- var
- Dummy: Integer;
- begin
- Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, MapFileName, LinkerBugUnit,
- MapFileSize, JclDebugDataSize, Dummy);
- end;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName, MapFileName: TFileName;
- out LinkerBugUnit: string; out MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean;
- var
- BinDebug: TJclBinDebugGenerator;
- begin
- BinDebug := TJclBinDebugGenerator.Create(MapFileName, 0);
- try
- Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, BinDebug,
- LinkerBugUnit, MapFileSize, JclDebugDataSize, LineNumberErrors);
- finally
- BinDebug.Free;
- end;
- end;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
- BinDebug: TJclBinDebugGenerator; out LinkerBugUnit: string;
- out MapFileSize, JclDebugDataSize: Integer): Boolean;
- var
- Dummy: Integer;
- begin
- Result := InsertDebugDataIntoExecutableFile(ExecutableFileName, BinDebug, LinkerBugUnit,
- MapFileSize, JclDebugDataSize, Dummy);
- end;
- function InsertDebugDataIntoExecutableFile(const ExecutableFileName: TFileName;
- BinDebug: TJclBinDebugGenerator; out LinkerBugUnit: string;
- out MapFileSize, JclDebugDataSize, LineNumberErrors: Integer): Boolean;
- var
- ImageStream: TStream;
- NtHeaders32: TImageNtHeaders32;
- NtHeaders64: TImageNtHeaders64;
- ImageSectionHeaders: TImageSectionHeaderArray;
- NtHeadersPosition, ImageSectionHeadersPosition, JclDebugSectionPosition: Int64;
- JclDebugSection: TImageSectionHeader;
- LastSection: PImageSectionHeader;
- VirtualAlignedSize: DWORD;
- NeedFill: Integer;
- procedure RoundUpToAlignment(var Value: DWORD; Alignment: DWORD);
- begin
- if (Value mod Alignment) <> 0 then
- Value := ((Value div Alignment) + 1) * Alignment;
- end;
- procedure MovePointerToRawData(AOffset: DWORD);
- var
- I: Integer;
- begin
- for I := Low(ImageSectionHeaders) to High(ImageSectionHeaders) do
- ImageSectionHeaders[I].PointerToRawData := ImageSectionHeaders[I].PointerToRawData + AOffset;
- end;
- procedure FillZeros(AStream: TStream; ACount: Integer);
- var
- I: Integer;
- X: array[0..511] of Byte;
- begin
- if ACount > 0 then
- begin
- if ACount > Length(X) then
- FillChar(X, SizeOf(X), 0)
- else
- FillChar(X, ACount, 0);
- while ACount > 0 do
- begin
- I := ACount;
- if I > SizeOf(X) then
- I := SizeOf(X);
- AStream.WriteBuffer(X, I);
- Dec(ACount, I);
- end;
- end;
- end;
- procedure WriteSectionHeaders(AStream: TStream; APosition: Integer);
- var
- HeaderSize: Integer;
- begin
- HeaderSize := SizeOf(TImageSectionHeader) * Length(ImageSectionHeaders);
- if (AStream.Seek(APosition, soFromBeginning) <> APosition) or
- (AStream.Write(ImageSectionHeaders[0], HeaderSize) <> HeaderSize) then
- raise EJclPeImageError.CreateRes(@SWriteError);
- FillZeros(AStream, ImageSectionHeaders[0].PointerToRawData - AStream.Position);
- end;
- procedure MoveData(AStream: TStream; AStart, AOffset: Integer);
- var
- CurPos: Integer;
- CurSize: Integer;
- Buffer: array of Byte;
- StartPos: Integer;
- begin
- SetLength(Buffer, 1024 * 1024);
- CurPos := AStream.Size - Length(Buffer);
- StartPos := ImageSectionHeaders[0].PointerToRawData;
- while CurPos > StartPos do
- begin
- if (AStream.Seek(CurPos, soBeginning) <> CurPos) or
- (AStream.Read(Buffer[0], Length(Buffer)) <> Length(Buffer)) then
- raise EJclPeImageError.CreateRes(@SReadError);
- if (AStream.Seek(CurPos + AOffset, soBeginning) <> CurPos + AOffset) or
- (AStream.Write(Buffer[0], Length(Buffer)) <> Length(Buffer)) then
- raise EJclPeImageError.CreateRes(@SWriteError);
- Dec(CurPos, Length(Buffer));
- end;
- CurSize := Length(Buffer) + CurPos - StartPos;
- if (AStream.Seek(StartPos, soBeginning) <> StartPos) or
- (AStream.Read(Buffer[0], CurSize) <> CurSize) then
- raise EJclPeImageError.CreateRes(@SReadError);
- if (AStream.Seek(StartPos + AOffset, soBeginning) <> StartPos + AOffset) or
- (AStream.Write(Buffer[0], CurSize) <> CurSize) then
- raise EJclPeImageError.CreateRes(@SWriteError);
- end;
- procedure CheckHeadersSpace(AStream: TStream);
- begin
- if ImageSectionHeaders[0].PointerToRawData < ImageSectionHeadersPosition +
- (SizeOf(TImageSectionHeader) * (Length(ImageSectionHeaders) + 1)) then
- begin
- MoveData(AStream, ImageSectionHeaders[0].PointerToRawData, NtHeaders64.OptionalHeader.FileAlignment);
- MovePointerToRawData(NtHeaders64.OptionalHeader.FileAlignment);
- WriteSectionHeaders(AStream, ImageSectionHeadersPosition);
- end;
- end;
- begin
- MapFileSize := 0;
- JclDebugDataSize := 0;
- LineNumberErrors := 0;
- LinkerBugUnit := '';
- if BinDebug.Stream <> nil then
- begin
- Result := True;
- if BinDebug.LinkerBug then
- begin
- LinkerBugUnit := BinDebug.LinkerBugUnitName;
- LineNumberErrors := BinDebug.LineNumberErrors;
- end;
- end
- else
- Result := False;
- if not Result then
- Exit;
- ImageStream := TFileStream.Create(ExecutableFileName, fmOpenReadWrite or fmShareExclusive);
- try
- try
- MapFileSize := BinDebug.Stream.Size;
- JclDebugDataSize := BinDebug.DataStream.Size;
- VirtualAlignedSize := JclDebugDataSize;
- // JCLDEBUG
- ResetMemory(JclDebugSection, SizeOf(JclDebugSection));
- // JCLDEBUG Virtual Size
- JclDebugSection.Misc.VirtualSize := JclDebugDataSize;
- // JCLDEBUG Raw data size
- JclDebugSection.SizeOfRawData := JclDebugDataSize;
- // JCLDEBUG Section name
- Move(JclDbgDataResName, JclDebugSection.Name, IMAGE_SIZEOF_SHORT_NAME);
- // JCLDEBUG Characteristics flags
- JclDebugSection.Characteristics := IMAGE_SCN_MEM_READ or IMAGE_SCN_CNT_INITIALIZED_DATA;
- case PeMapImgTarget(ImageStream, 0) of
- taWin32:
- begin
- NtHeadersPosition := PeMapImgNtHeaders32(ImageStream, 0, NtHeaders32);
- Assert(NtHeadersPosition <> -1);
- ImageSectionHeadersPosition := PeMapImgSections32(ImageStream, NtHeadersPosition, NtHeaders32, ImageSectionHeaders);
- Assert(ImageSectionHeadersPosition <> -1);
- // Check whether there is not a section with the name already. If so, return True (0000069)
- if PeMapImgFindSection(ImageSectionHeaders, JclDbgDataResName) <> -1 then
- begin
- Result := True;
- Exit;
- end;
- JclDebugSectionPosition := ImageSectionHeadersPosition + (SizeOf(ImageSectionHeaders[0]) * Length(ImageSectionHeaders));
- LastSection := @ImageSectionHeaders[High(ImageSectionHeaders)];
- // Increase the number of sections
- Inc(NtHeaders32.FileHeader.NumberOfSections);
- // JCLDEBUG Virtual Address
- JclDebugSection.VirtualAddress := LastSection^.VirtualAddress + LastSection^.Misc.VirtualSize;
- // JCLDEBUG Physical Offset
- JclDebugSection.PointerToRawData := LastSection^.PointerToRawData + LastSection^.SizeOfRawData;
- // JCLDEBUG section rounding :
- RoundUpToAlignment(JclDebugSection.VirtualAddress, NtHeaders32.OptionalHeader.SectionAlignment);
- RoundUpToAlignment(JclDebugSection.PointerToRawData, NtHeaders32.OptionalHeader.FileAlignment);
- RoundUpToAlignment(JclDebugSection.SizeOfRawData, NtHeaders32.OptionalHeader.FileAlignment);
- // Size of virtual data area
- RoundUpToAlignment(VirtualAlignedSize, NtHeaders32.OptionalHeader.SectionAlignment);
- // Update Size of Image
- Inc(NtHeaders32.OptionalHeader.SizeOfImage, VirtualAlignedSize);
- // Update Initialized data size
- Inc(NtHeaders32.OptionalHeader.SizeOfInitializedData, JclDebugSection.SizeOfRawData);
- // write NT Headers 32
- if (ImageStream.Seek(NtHeadersPosition, soBeginning) <> NtHeadersPosition) or
- (ImageStream.Write(NtHeaders32, SizeOf(NtHeaders32)) <> SizeOf(NtHeaders32)) then
- raise EJclPeImageError.CreateRes(@SWriteError);
- end;
- taWin64:
- begin
- NtHeadersPosition := PeMapImgNtHeaders64(ImageStream, 0, NtHeaders64);
- Assert(NtHeadersPosition <> -1);
- ImageSectionHeadersPosition := PeMapImgSections64(ImageStream, NtHeadersPosition, NtHeaders64, ImageSectionHeaders);
- Assert(ImageSectionHeadersPosition <> -1);
- // Check whether there is not a section with the name already. If so, return True (0000069)
- if PeMapImgFindSection(ImageSectionHeaders, JclDbgDataResName) <> -1 then
- begin
- Result := True;
- Exit;
- end;
- // Check if there is enough space for additional header
- CheckHeadersSpace(ImageStream);
- JclDebugSectionPosition := ImageSectionHeadersPosition + (SizeOf(ImageSectionHeaders[0]) * Length(ImageSectionHeaders));
- LastSection := @ImageSectionHeaders[High(ImageSectionHeaders)];
- // Increase the number of sections
- Inc(NtHeaders64.FileHeader.NumberOfSections);
- // JCLDEBUG Virtual Address
- JclDebugSection.VirtualAddress := LastSection^.VirtualAddress + LastSection^.Misc.VirtualSize;
- // JCLDEBUG Physical Offset
- JclDebugSection.PointerToRawData := LastSection^.PointerToRawData + LastSection^.SizeOfRawData;
- // JCLDEBUG section rounding :
- RoundUpToAlignment(JclDebugSection.VirtualAddress, NtHeaders64.OptionalHeader.SectionAlignment);
- RoundUpToAlignment(JclDebugSection.PointerToRawData, NtHeaders64.OptionalHeader.FileAlignment);
- RoundUpToAlignment(JclDebugSection.SizeOfRawData, NtHeaders64.OptionalHeader.FileAlignment);
- // Size of virtual data area
- RoundUpToAlignment(VirtualAlignedSize, NtHeaders64.OptionalHeader.SectionAlignment);
- // Update Size of Image
- Inc(NtHeaders64.OptionalHeader.SizeOfImage, VirtualAlignedSize);
- // Update Initialized data size
- Inc(NtHeaders64.OptionalHeader.SizeOfInitializedData, JclDebugSection.SizeOfRawData);
- // write NT Headers 64
- if (ImageStream.Seek(NtHeadersPosition, soBeginning) <> NtHeadersPosition) or
- (ImageStream.Write(NtHeaders64, SizeOf(NtHeaders64)) <> SizeOf(NtHeaders64)) then
- raise EJclPeImageError.CreateRes(@SWriteError);
- end;
- else
- Result := False;
- Exit;
- end;
- // write section header
- if (ImageStream.Seek(JclDebugSectionPosition, soBeginning) <> JclDebugSectionPosition) or
- (ImageStream.Write(JclDebugSection, SizeOf(JclDebugSection)) <> SizeOf(JclDebugSection)) then
- raise EJclPeImageError.CreateRes(@SWriteError);
- // Fill data to alignment
- NeedFill := INT_PTR(JclDebugSection.SizeOfRawData) - JclDebugDataSize;
- // Note: Delphi linker seems to generate incorrect (unaligned) size of
- // the executable when adding TD32 debug data so the position could be
- // behind the size of the file then.
- ImageStream.Seek({0 +} JclDebugSection.PointerToRawData, soBeginning);
- ImageStream.CopyFrom(BinDebug.DataStream, 0);
- FillZeros(ImageStream, NeedFill);
- except
- Result := False;
- end;
- finally
- ImageStream.Free;
- end;
- end;
- //=== { TJclBinDebugGenerator } ==============================================
- constructor TJclBinDebugGenerator.Create(const MapFileName: TFileName; Module: HMODULE);
- begin
- inherited Create(MapFileName, Module);
- FDataStream := TMemoryStream.Create;
- FMapFileName := MapFileName;
- if FStream <> nil then
- CreateData;
- end;
- destructor TJclBinDebugGenerator.Destroy;
- begin
- FreeAndNil(FDataStream);
- inherited Destroy;
- end;
- {$OVERFLOWCHECKS OFF}
- function TJclBinDebugGenerator.CalculateCheckSum: Boolean;
- var
- Header: PJclDbgHeader;
- P, EndData: PAnsiChar;
- CheckSum: Integer;
- begin
- Result := DataStream.Size >= SizeOf(TJclDbgHeader);
- if Result then
- begin
- P := DataStream.Memory;
- EndData := P + DataStream.Size;
- Header := PJclDbgHeader(P);
- CheckSum := 0;
- Header^.CheckSum := 0;
- Header^.CheckSumValid := True;
- while P < EndData do
- begin
- Inc(CheckSum, PInteger(P)^);
- Inc(PInteger(P));
- end;
- Header^.CheckSum := CheckSum;
- end;
- end;
- {$IFDEF OVERFLOWCHECKS_ON}
- {$OVERFLOWCHECKS ON}
- {$ENDIF OVERFLOWCHECKS_ON}
- procedure TJclBinDebugGenerator.CreateData;
- var
- {$IFDEF SUPPORTS_GENERICS}
- WordList: TDictionary<string, Integer>;
- {$ELSE}
- WordList: TStringList;
- {$ENDIF SUPPORTS_GENERICS}
- WordStream: TMemoryStream;
- LastSegmentID: Word;
- LastSegmentStored: Boolean;
- function PosLastNameSep(const S: string): Integer;
- var
- InGeneric: Integer;
- begin
- // Unit.Name.ProcName => "Unit.Name" + "ProcName"
- // Unit.Name..ClassName => "UnitName" + ".ClassName"
- // Unit.Name.Class<Unit.Name.OtherClass>.ProcName => "Unit.Name.Class<Unit.Name.OtherClass>" + "ProcName"
- InGeneric := 0;
- for Result := Length(S) downto 1 do
- begin
- case S[Result] of
- '.':
- if InGeneric = 0 then
- if (Result = 1) or (S[Result - 1] <> '.') then
- Exit;
- '>':
- Inc(InGeneric);
- '<':
- Dec(InGeneric);
- end;
- end;
- Result := 0;
- end;
- function AddWord(const S: string): Integer;
- var
- {$IFDEF SUPPORTS_GENERICS}
- LowerS: string;
- {$ELSE}
- N: Integer;
- {$ENDIF SUPPORTS_GENERICS}
- E: AnsiString;
- begin
- if S = '' then
- begin
- Result := 0;
- Exit;
- end;
- {$IFDEF SUPPORTS_GENERICS}
- LowerS := AnsiLowerCase(S);
- if not WordList.TryGetValue(LowerS, Result) then
- begin
- Result := WordStream.Position;
- E := EncodeNameString(S);
- WordStream.Write(E[1], Length(E));
- WordList.Add(LowerS, Result);
- end;
- {$ELSE} // for large map files this is very slow
- N := WordList.IndexOf(S);
- if N = -1 then
- begin
- Result := WordStream.Position;
- E := EncodeNameString(S);
- WordStream.Write(E[1], Length(E));
- WordList.AddObject(S, TObject(Result));
- end
- else
- Result := DWORD(WordList.Objects[N]);
- {$ENDIF SUPPORTS_GENERICS}
- Inc(Result);
- end;
- procedure WriteValue(Value: Integer);
- var
- L: Integer;
- D: DWORD;
- P: array [1..5] of Byte;
- begin
- D := Value and $FFFFFFFF;
- L := 0;
- while D > $7F do
- begin
- Inc(L);
- P[L] := (D and $7F) or $80;
- D := D shr 7;
- end;
- Inc(L);
- P[L] := (D and $7F);
- FDataStream.Write(P, L);
- end;
- procedure WriteValueOfs(Value: Integer; var LastValue: Integer);
- begin
- WriteValue(Value - LastValue);
- LastValue := Value;
- end;
- function IsSegmentStored(SegID: Word): Boolean;
- var
- SegIndex: Integer;
- GroupName: string;
- begin
- if SegID <> LastSegmentID then
- begin
- LastSegmentID := $FFFF;
- LastSegmentStored := False;
- for SegIndex := Low(FSegmentClasses) to High(FSegmentClasses) do
- if FSegmentClasses[SegIndex].Segment = SegID then
- begin
- LastSegmentID := FSegmentClasses[SegIndex].Segment;
- GroupName := MapStringCacheToStr(FSegmentClasses[SegIndex].GroupName);
- LastSegmentStored := (GroupName = 'CODE') or (GroupName = 'ICODE');
- Break;
- end;
- end;
- Result := LastSegmentStored;
- end;
- const
- AlignBytes: array[0..2] of Byte = (0, 0, 0);
- var
- FileHeader: TJclDbgHeader;
- I, D: Integer;
- S: string;
- L1, L2, L3: Integer;
- FirstWord, SecondWord: Integer;
- WordStreamSize, DataStreamSize: Int64;
- begin
- LastSegmentID := $FFFF;
- WordStream := TMemoryStream.Create;
- {$IFDEF SUPPORTS_GENERICS}
- WordList := TDictionary<string, Integer>.Create(Length(FSourceNames) + Length(FProcNames));
- {$ELSE}
- WordList := TStringList.Create;
- {$ENDIF SUPPORTS_GENERICS}
- try
- {$IFNDEF SUPPORTS_GENERICS}
- WordList.Sorted := True;
- WordList.Duplicates := dupError;
- {$ENDIF ~SUPPORTS_GENERICS}
- WordStream.SetSize((Length(FSourceNames) + Length(FProcNames)) * 40); // take an average of 40 chars per identifier
- FileHeader.Signature := JclDbgDataSignature;
- FileHeader.Version := JclDbgHeaderVersion;
- FileHeader.CheckSum := 0;
- FileHeader.CheckSumValid := False;
- FileHeader.ModuleName := AddWord(PathExtractFileNameNoExt(FMapFileName));
- FDataStream.WriteBuffer(FileHeader, SizeOf(FileHeader));
- FileHeader.Units := FDataStream.Position;
- L1 := 0;
- L2 := 0;
- for I := 0 to Length(FSegments) - 1 do
- if IsSegmentStored(FSegments[I].Segment) then
- begin
- WriteValueOfs(FSegments[I].StartVA, L1);
- WriteValueOfs(AddWord(MapStringCacheToModuleName(FSegments[I].UnitName)), L2);
- end;
- WriteValue(MaxInt);
- FileHeader.SourceNames := FDataStream.Position;
- L1 := 0;
- L2 := 0;
- for I := 0 to Length(FSourceNames) - 1 do
- if IsSegmentStored(FSourceNames[I].Segment) then
- begin
- // FSourceNames[] is sorted by VA, so if the source file name is the same as the previous
- // we don't need to store it because the VA will be matched by the previous entry.
- // This removes a lot of "Generics.Collections.pas" entries.
- S := MapStringCacheToStr(FSourceNames[I].ProcName);
- if (I = 0) or (FSourceNames[I - 1].ProcName.CachedValue <> S) then
- begin
- WriteValueOfs(FSourceNames[I].VA, L1);
- WriteValueOfs(AddWord(S), L2);
- end;
- end;
- WriteValue(MaxInt);
- FileHeader.Symbols := FDataStream.Position;
- L1 := 0;
- L2 := 0;
- L3 := 0;
- for I := 0 to Length(FProcNames) - 1 do
- if IsSegmentStored(FProcNames[I].Segment) then
- begin
- WriteValueOfs(FProcNames[I].VA, L1);
- // MAP files generated by C++Builder have spaces in their names
- S := MapStringCacheToStr(FProcNames[I].ProcName, True);
- D := PosLastNameSep(S);
- if D = 1 then
- begin
- FirstWord := 0;
- SecondWord := 0;
- end
- else
- if D = 0 then
- begin
- FirstWord := AddWord(S);
- SecondWord := 0;
- end
- else
- begin
- FirstWord := AddWord(Copy(S, 1, D - 1));
- SecondWord := AddWord(Copy(S, D + 1, Length(S)));
- end;
- WriteValueOfs(FirstWord, L2);
- WriteValueOfs(SecondWord, L3);
- end;
- WriteValue(MaxInt);
- FileHeader.LineNumbers := FDataStream.Position;
- L1 := 0;
- L2 := 0;
- for I := 0 to Length(FLineNumbers) - 1 do
- if IsSegmentStored(FLineNumbers[I].Segment) then
- begin
- WriteValueOfs(FLineNumbers[I].VA, L1);
- WriteValueOfs(FLineNumbers[I].LineNumber, L2);
- end;
- WriteValue(MaxInt);
- FileHeader.Words := FDataStream.Position;
- // Calculate and allocate the required size in advance instead of reallocating on the fly.
- WordStreamSize := WordStream.Position;
- DataStreamSize := FDataStream.Position + WordStreamSize;
- DataStreamSize := DataStreamSize + (4 - (DataStreamSize and $3));
- FDataStream.Size := DataStreamSize; // set capacity
- WordStream.Position := 0;
- FDataStream.CopyFrom(WordStream, WordStreamSize);
- // Align to 4 bytes
- FDataStream.WriteBuffer(AlignBytes, 4 - (FDataStream.Position and $3));
- if FDataStream.Size <> FDataStream.Position then // just in case something changed without adjusting the size calculation
- FDataStream.Size := FDataStream.Position;
- // Update the file header
- FDataStream.Seek(0, soBeginning);
- FDataStream.WriteBuffer(FileHeader, SizeOf(FileHeader));
- finally
- WordStream.Free;
- WordList.Free;
- end;
- end;
- //=== { TJclBinDebugScanner } ================================================
- constructor TJclBinDebugScanner.Create(AStream: TCustomMemoryStream; CacheData, CacheProcNames: Boolean);
- begin
- inherited Create;
- FCacheData := CacheData;
- FCacheProcNames := CacheProcNames;
- FStream := AStream;
- CheckFormat;
- end;
- procedure TJclBinDebugScanner.CacheLineNumbers;
- var
- P: Pointer;
- Value, LineNumber, C, Ln: Integer;
- CurrVA: DWORD;
- begin
- if FLineNumbers = nil then
- begin
- LineNumber := 0;
- CurrVA := 0;
- C := 0;
- Ln := 0;
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.LineNumbers);
- Value := 0;
- while ReadValue(P, Value) do
- begin
- Inc(CurrVA, Value);
- ReadValue(P, Value);
- Inc(LineNumber, Value);
- if C = Ln then
- begin
- if Ln < 64 then
- Ln := 64
- else
- Ln := Ln + Ln div 4;
- SetLength(FLineNumbers, Ln);
- end;
- FLineNumbers[C].VA := CurrVA;
- FLineNumbers[C].LineNumber := LineNumber;
- Inc(C);
- end;
- SetLength(FLineNumbers, C);
- end;
- end;
- procedure TJclBinDebugScanner.CacheProcNames;
- var
- P: Pointer;
- Value, FirstWord, SecondWord, C, Ln: Integer;
- CurrAddr: DWORD;
- begin
- if FProcNames = nil then
- begin
- FirstWord := 0;
- SecondWord := 0;
- CurrAddr := 0;
- C := 0;
- Ln := 0;
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.Symbols);
- Value := 0;
- while ReadValue(P, Value) do
- begin
- Inc(CurrAddr, Value);
- ReadValue(P, Value);
- Inc(FirstWord, Value);
- ReadValue(P, Value);
- Inc(SecondWord, Value);
- if C = Ln then
- begin
- if Ln < 64 then
- Ln := 64
- else
- Ln := Ln + Ln div 4;
- SetLength(FProcNames, Ln);
- end;
- FProcNames[C].Addr := CurrAddr;
- FProcNames[C].FirstWord := FirstWord;
- FProcNames[C].SecondWord := SecondWord;
- if FCacheProcNames then
- begin
- if (FirstWord <> 0) and (SecondWord <> 0) then
- FProcNames[C].Text := DataToStr(FirstWord) + '.' + DataToStr(SecondWord)
- else if FirstWord <> 0 then
- FProcNames[C].Text := DataToStr(FirstWord)
- else
- FProcNames[C].Text := '';
- end
- else
- FProcNames[C].Text := '';
- Inc(C);
- end;
- SetLength(FProcNames, C);
- end;
- end;
- {$OVERFLOWCHECKS OFF}
- procedure TJclBinDebugScanner.CheckFormat;
- var
- CheckSum: Integer;
- Data, EndData: PAnsiChar;
- Header: PJclDbgHeader;
- begin
- Data := FStream.Memory;
- Header := PJclDbgHeader(Data);
- FValidFormat := (Data <> nil) and (FStream.Size > SizeOf(TJclDbgHeader)) and
- (FStream.Size mod 4 = 0) and
- (Header^.Signature = JclDbgDataSignature) and (Header^.Version = JclDbgHeaderVersion);
- if FValidFormat and Header^.CheckSumValid then
- begin
- CheckSum := -Header^.CheckSum;
- EndData := Data + FStream.Size;
- while Data < EndData do
- begin
- Inc(CheckSum, PInteger(Data)^);
- Inc(PInteger(Data));
- end;
- CheckSum := (CheckSum shr 8) or (CheckSum shl 24);
- FValidFormat := (CheckSum = Header^.CheckSum);
- end;
- end;
- {$IFDEF OVERFLOWCHECKS_ON}
- {$OVERFLOWCHECKS ON}
- {$ENDIF OVERFLOWCHECKS_ON}
- function TJclBinDebugScanner.DataToStr(A: Integer): string;
- var
- P: PAnsiChar;
- begin
- if A = 0 then
- Result := ''
- else
- begin
- P := PAnsiChar(TJclAddr(FStream.Memory) + TJclAddr(A) + TJclAddr(PJclDbgHeader(FStream.Memory)^.Words) - 1);
- Result := DecodeNameString(P);
- end;
- end;
- function TJclBinDebugScanner.GetModuleName: string;
- begin
- Result := DataToStr(PJclDbgHeader(FStream.Memory)^.ModuleName);
- end;
- function TJclBinDebugScanner.IsModuleNameValid(const Name: TFileName): Boolean;
- begin
- Result := AnsiSameText(ModuleName, PathExtractFileNameNoExt(Name));
- end;
- function TJclBinDebugScanner.LineNumberFromAddr(Addr: DWORD): Integer;
- var
- Dummy: Integer;
- begin
- Result := LineNumberFromAddr(Addr, Dummy);
- end;
- function TJclBinDebugScanner.LineNumberFromAddr(Addr: DWORD; out Offset: Integer): Integer;
- var
- P: Pointer;
- Value, LineNumber: Integer;
- CurrVA, ModuleStartVA, ItemVA: DWORD;
- begin
- ModuleStartVA := ModuleStartFromAddr(Addr);
- LineNumber := 0;
- Offset := 0;
- if FCacheData then
- begin
- CacheLineNumbers;
- for Value := Length(FLineNumbers) - 1 downto 0 do
- if FLineNumbers[Value].VA <= Addr then
- begin
- if FLineNumbers[Value].VA >= ModuleStartVA then
- begin
- LineNumber := FLineNumbers[Value].LineNumber;
- Offset := Addr - FLineNumbers[Value].VA;
- end;
- Break;
- end;
- end
- else
- begin
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.LineNumbers);
- CurrVA := 0;
- ItemVA := 0;
- while ReadValue(P, Value) do
- begin
- Inc(CurrVA, Value);
- if Addr < CurrVA then
- begin
- if ItemVA < ModuleStartVA then
- begin
- LineNumber := 0;
- Offset := 0;
- end;
- Break;
- end
- else
- begin
- ItemVA := CurrVA;
- ReadValue(P, Value);
- Inc(LineNumber, Value);
- Offset := Addr - CurrVA;
- end;
- end;
- end;
- Result := LineNumber;
- end;
- function TJclBinDebugScanner.MakePtr(A: Integer): Pointer;
- begin
- Result := Pointer(TJclAddr(FStream.Memory) + TJclAddr(A));
- end;
- function TJclBinDebugScanner.ModuleNameFromAddr(Addr: DWORD): string;
- var
- Value, Name: Integer;
- StartAddr: DWORD;
- P: Pointer;
- begin
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.Units);
- Name := 0;
- StartAddr := 0;
- Value := 0;
- while ReadValue(P, Value) do
- begin
- Inc(StartAddr, Value);
- if Addr < StartAddr then
- Break
- else
- begin
- ReadValue(P, Value);
- Inc(Name, Value);
- end;
- end;
- Result := DataToStr(Name);
- end;
- function TJclBinDebugScanner.ModuleStartFromAddr(Addr: DWORD): DWORD;
- var
- Value: Integer;
- StartAddr, ModuleStartAddr: DWORD;
- P: Pointer;
- begin
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.Units);
- StartAddr := 0;
- ModuleStartAddr := DWORD(-1);
- Value := 0;
- while ReadValue(P, Value) do
- begin
- Inc(StartAddr, Value);
- if Addr < StartAddr then
- Break
- else
- begin
- ReadValue(P, Value);
- ModuleStartAddr := StartAddr;
- end;
- end;
- Result := ModuleStartAddr;
- end;
- function TJclBinDebugScanner.ProcNameFromAddr(Addr: DWORD): string;
- var
- Dummy: Integer;
- begin
- Result := ProcNameFromAddr(Addr, Dummy);
- end;
- function TJclBinDebugScanner.ProcNameFromAddr(Addr: DWORD; out Offset: Integer): string;
- var
- P: Pointer;
- Value, FirstWord, SecondWord: Integer;
- CurrAddr, ModuleStartAddr, ItemAddr: DWORD;
- begin
- ModuleStartAddr := ModuleStartFromAddr(Addr);
- FirstWord := 0;
- SecondWord := 0;
- Offset := 0;
- if FCacheData then
- begin
- CacheProcNames;
- for Value := Length(FProcNames) - 1 downto 0 do
- if FProcNames[Value].Addr <= Addr then
- begin
- if FProcNames[Value].Addr >= ModuleStartAddr then
- begin
- FirstWord := FProcNames[Value].FirstWord;
- SecondWord := FProcNames[Value].SecondWord;
- Offset := Addr - FProcNames[Value].Addr;
- end;
- Break;
- end;
- end
- else
- begin
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.Symbols);
- CurrAddr := 0;
- ItemAddr := 0;
- while ReadValue(P, Value) do
- begin
- Inc(CurrAddr, Value);
- if Addr < CurrAddr then
- begin
- if ItemAddr < ModuleStartAddr then
- begin
- FirstWord := 0;
- SecondWord := 0;
- Offset := 0;
- end;
- Break;
- end
- else
- begin
- ItemAddr := CurrAddr;
- ReadValue(P, Value);
- Inc(FirstWord, Value);
- ReadValue(P, Value);
- Inc(SecondWord, Value);
- Offset := Addr - CurrAddr;
- end;
- end;
- end;
- if FirstWord <> 0 then
- begin
- Result := DataToStr(FirstWord);
- if SecondWord <> 0 then
- Result := Result + '.' + DataToStr(SecondWord);
- end
- else
- Result := '';
- end;
- class function TJclBinDebugScanner.ReadValue(var P: Pointer; var Value: Integer): Boolean;
- var
- N: Integer;
- I: Integer;
- B: Byte;
- begin
- N := 0;
- I := 0;
- repeat
- B := PByte(P)^;
- Inc(PByte(P));
- Inc(N, (B and $7F) shl I);
- Inc(I, 7);
- until B and $80 = 0;
- Value := N;
- Result := (N <> MaxInt);
- end;
- function TJclBinDebugScanner.SourceNameFromAddr(Addr: DWORD): string;
- var
- Value, Name: Integer;
- StartAddr, ModuleStartAddr, ItemAddr: DWORD;
- P: Pointer;
- Found: Boolean;
- begin
- ModuleStartAddr := ModuleStartFromAddr(Addr);
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.SourceNames);
- Name := 0;
- StartAddr := 0;
- ItemAddr := 0;
- Found := False;
- Value := 0;
- while ReadValue(P, Value) do
- begin
- Inc(StartAddr, Value);
- if Addr < StartAddr then
- begin
- if ItemAddr < ModuleStartAddr then
- Name := 0
- else
- Found := True;
- Break;
- end
- else
- begin
- ItemAddr := StartAddr;
- ReadValue(P, Value);
- Inc(Name, Value);
- end;
- end;
- if Found then
- Result := DataToStr(Name)
- else
- Result := '';
- end;
- function TJclBinDebugScanner.VAFromUnitAndProcName(const UnitName, ProcName: string): DWORD;
- var
- P: Pointer;
- VA: DWORD;
- I, Value: Integer;
- FirstWord, SecondWord: Integer;
- QualifiedName, S: string;
- begin
- Result := 0;
- if (UnitName = '') or (ProcName = '') then
- Exit;
- QualifiedName := UnitName + '.' + ProcName;
- if FCacheData then
- begin
- CacheProcNames;
- for I := Low(FProcNames) to High(FProcNames) do
- begin
- if FProcNames[I].Text <> '' then
- S := FProcNames[I].Text
- else
- begin
- if FProcNames[I].FirstWord = 0 then
- Continue;
- if (FProcNames[I].FirstWord <> 0) and (FProcNames[I].SecondWord <> 0) then
- FProcNames[I].Text := DataToStr(FProcNames[I].FirstWord ) + '.' + DataToStr(FProcNames[I].SecondWord)
- else if FProcNames[I].FirstWord <> 0 then
- FProcNames[I].Text := DataToStr(FProcNames[I].FirstWord)
- else
- FProcNames[I].Text := '';
- end;
- if CompareText(FProcNames[I].Text, QualifiedName) = 0 then
- begin
- Result := FProcNames[i].Addr;
- Break;
- end;
- end;
- end
- else
- begin
- P := MakePtr(PJclDbgHeader(FStream.Memory)^.Symbols);
- VA := 0;
- FirstWord := 0;
- SecondWord := 0;
- while ReadValue(P, Value) do
- begin
- Inc(VA, Value);
- ReadValue(P, Value);
- Inc(FirstWord, Value);
- ReadValue(P, Value);
- Inc(SecondWord, Value);
- if FirstWord = 0 then
- Continue;
- S := DataToStr(FirstWord);
- if SecondWord <> 0 then
- S := S + '.' + DataToStr(SecondWord);
- if CompareText(S, QualifiedName) = 0 then
- begin
- Result := VA;
- Break;
- end;
- end;
- end;
- end;
- //=== { TJclLocationInfoEx } =================================================
- constructor TJclLocationInfoEx.Create(AParent: TJclCustomLocationInfoList; Address: Pointer);
- var
- Options: TJclLocationInfoListOptions;
- begin
- inherited Create;
- FAddress := Address;
- FParent := AParent;
- if Assigned(FParent) then
- Options := FParent.Options
- else
- Options := [];
- Fill(Options);
- end;
- procedure TJclLocationInfoEx.AssignTo(Dest: TPersistent);
- begin
- if Dest is TJclLocationInfoEx then
- begin
- TJclLocationInfoEx(Dest).FAddress := FAddress;
- TJclLocationInfoEx(Dest).FBinaryFileName := FBinaryFileName;
- TJclLocationInfoEx(Dest).FDebugInfo := FDebugInfo;
- TJclLocationInfoEx(Dest).FLineNumber := FLineNumber;
- TJclLocationInfoEx(Dest).FLineNumberOffsetFromProcedureStart := FLineNumberOffsetFromProcedureStart;
- TJclLocationInfoEx(Dest).FModuleName := FModuleName;
- TJclLocationInfoEx(Dest).FOffsetFromLineNumber := FOffsetFromLineNumber;
- TJclLocationInfoEx(Dest).FOffsetFromProcName := FOffsetFromProcName;
- TJclLocationInfoEx(Dest).FProcedureName := FProcedureName;
- TJclLocationInfoEx(Dest).FSourceName := FSourceName;
- TJclLocationInfoEx(Dest).FSourceUnitName := FSourceUnitName;
- TJclLocationInfoEx(Dest).FUnitVersionDateTime := FUnitVersionDateTime;
- TJclLocationInfoEx(Dest).FUnitVersionExtra := FUnitVersionExtra;
- TJclLocationInfoEx(Dest).FUnitVersionLogPath := FUnitVersionLogPath;
- TJclLocationInfoEx(Dest).FUnitVersionRCSfile := FUnitVersionRCSfile;
- TJclLocationInfoEx(Dest).FUnitVersionRevision := FUnitVersionRevision;
- TJclLocationInfoEx(Dest).FVAddress := FVAddress;
- TJclLocationInfoEx(Dest).FValues := FValues;
- end
- else
- inherited AssignTo(Dest);
- end;
- procedure TJclLocationInfoEx.Clear;
- begin
- FAddress := nil;
- Fill([]);
- end;
- procedure TJclLocationInfoEx.Fill(AOptions: TJclLocationInfoListOptions);
- var
- Info, StartProcInfo: TJclLocationInfo;
- FixedProcedureName: string;
- Module: HMODULE;
- {$IFDEF UNITVERSIONING}
- I: Integer;
- UnitVersion: TUnitVersion;
- UnitVersioning: TUnitVersioning;
- UnitVersioningModule: TUnitVersioningModule;
- {$ENDIF UNITVERSIONING}
- begin
- FValues := [];
- if liloAutoGetAddressInfo in AOptions then
- begin
- Module := ModuleFromAddr(FAddress);
- FVAddress := Pointer(TJclAddr(FAddress) - TJclAddr(Module) - ModuleCodeOffset);
- FModuleName := ExtractFileName(GetModulePath(Module));
- end
- else
- begin
- {$IFDEF UNITVERSIONING}
- Module := 0;
- {$ENDIF UNITVERSIONING}
- FVAddress := nil;
- FModuleName := '';
- end;
- if (liloAutoGetLocationInfo in AOptions) and GetLocationInfo(FAddress, Info) then
- begin
- FValues := FValues + [lievLocationInfo];
- FOffsetFromProcName := Info.OffsetFromProcName;
- FSourceUnitName := Info.UnitName;
- FixedProcedureName := Info.ProcedureName;
- if Pos(Info.UnitName + '.', FixedProcedureName) = 1 then
- FixedProcedureName := Copy(FixedProcedureName, Length(Info.UnitName) + 2, Length(FixedProcedureName) - Length(Info.UnitName) - 1);
- FProcedureName := FixedProcedureName;
- FSourceName := Info.SourceName;
- FLineNumber := Info.LineNumber;
- if FLineNumber > 0 then
- FOffsetFromLineNumber := Info.OffsetFromLineNumber
- else
- FOffsetFromLineNumber := 0;
- if GetLocationInfo(Pointer(TJclAddr(Info.Address) -
- Cardinal(Info.OffsetFromProcName)), StartProcInfo) and (StartProcInfo.LineNumber > 0) then
- begin
- FLineNumberOffsetFromProcedureStart := Info.LineNumber - StartProcInfo.LineNumber;
- FValues := FValues + [lievProcedureStartLocationInfo];
- end
- else
- FLineNumberOffsetFromProcedureStart := 0;
- FDebugInfo := Info.DebugInfo;
- FBinaryFileName := Info.BinaryFileName;
- end
- else
- begin
- FOffsetFromProcName := 0;
- FSourceUnitName := '';
- FProcedureName := '';
- FSourceName := '';
- FLineNumber := 0;
- FOffsetFromLineNumber := 0;
- FLineNumberOffsetFromProcedureStart := 0;
- FDebugInfo := nil;
- FBinaryFileName := '';
- end;
- FUnitVersionDateTime := 0;
- FUnitVersionLogPath := '';
- FUnitVersionRCSfile := '';
- FUnitVersionRevision := '';
- {$IFDEF UNITVERSIONING}
- if (liloAutoGetUnitVersionInfo in AOptions) and (FSourceName <> '') then
- begin
- if not (liloAutoGetAddressInfo in AOptions) then
- Module := ModuleFromAddr(FAddress);
- UnitVersioning := GetUnitVersioning;
- for I := 0 to UnitVersioning.ModuleCount - 1 do
- begin
- UnitVersioningModule := UnitVersioning.Modules[I];
- if UnitVersioningModule.Instance = Module then
- begin
- UnitVersion := UnitVersioningModule.FindUnit(FSourceName);
- if Assigned(UnitVersion) then
- begin
- FUnitVersionDateTime := UnitVersion.DateTime;
- FUnitVersionLogPath := UnitVersion.LogPath;
- FUnitVersionRCSfile := UnitVersion.RCSfile;
- FUnitVersionRevision := UnitVersion.Revision;
- FValues := FValues + [lievUnitVersionInfo];
- Break;
- end;
- end;
- if lievUnitVersionInfo in FValues then
- Break;
- end;
- end;
- {$ENDIF UNITVERSIONING}
- end;
- { TODO -oUSc : Include... better as function than property? }
- function TJclLocationInfoEx.GetAsString: string;
- const
- IncludeStartProcLineOffset = True;
- IncludeAddressOffset = True;
- IncludeModuleName = True;
- var
- IncludeVAddress: Boolean;
- OffsetStr, StartProcOffsetStr: string;
- begin
- IncludeVAddress := True;
- OffsetStr := '';
- if lievLocationInfo in FValues then
- begin
- if LineNumber > 0 then
- begin
- if IncludeStartProcLineOffset and (lievProcedureStartLocationInfo in FValues) then
- StartProcOffsetStr := Format(' + %d', [LineNumberOffsetFromProcedureStart])
- else
- StartProcOffsetStr := '';
- if IncludeAddressOffset then
- begin
- if OffsetFromLineNumber >= 0 then
- OffsetStr := Format(' + $%x', [OffsetFromLineNumber])
- else
- OffsetStr := Format(' - $%x', [-OffsetFromLineNumber])
- end;
- Result := Format('[%p] %s.%s (Line %u, "%s"%s)%s', [Address, SourceUnitName, ProcedureName, LineNumber,
- SourceName, StartProcOffsetStr, OffsetStr]);
- end
- else
- begin
- if IncludeAddressOffset then
- OffsetStr := Format(' + $%x', [OffsetFromProcName]);
- if SourceUnitName <> '' then
- Result := Format('[%p] %s.%s%s', [Address, SourceUnitName, ProcedureName, OffsetStr])
- else
- Result := Format('[%p] %s%s', [Address, ProcedureName, OffsetStr]);
- end;
- end
- else
- begin
- Result := Format('[%p]', [Address]);
- IncludeVAddress := True;
- end;
- if IncludeVAddress or IncludeModuleName then
- begin
- if IncludeVAddress then
- begin
- OffsetStr := Format('(%p) ', [VAddress]);
- Result := OffsetStr + Result;
- end;
- if IncludeModuleName then
- Insert(Format('{%-12s}', [ModuleName]), Result, 11 {$IFDEF CPUX64}+ 8{$ENDIF});
- end;
- end;
- //=== { TJclCustomLocationInfoList } =========================================
- constructor TJclCustomLocationInfoList.Create;
- begin
- inherited Create;
- FItemClass := TJclLocationInfoEx;
- FItems := TObjectList.Create;
- FOptions := [];
- end;
- destructor TJclCustomLocationInfoList.Destroy;
- begin
- FItems.Free;
- inherited Destroy;
- end;
- procedure TJclCustomLocationInfoList.AddStackInfoList(AStackInfoList: TObject);
- var
- I: Integer;
- begin
- TJclStackInfoList(AStackInfoList).ForceStackTracing;
- for I := 0 to TJclStackInfoList(AStackInfoList).Count - 1 do
- InternalAdd(TJclStackInfoList(AStackInfoList)[I].CallerAddr);
- end;
- procedure TJclCustomLocationInfoList.AssignTo(Dest: TPersistent);
- var
- I: Integer;
- begin
- if Dest is TJclCustomLocationInfoList then
- begin
- TJclCustomLocationInfoList(Dest).Clear;
- for I := 0 to Count - 1 do
- TJclCustomLocationInfoList(Dest).InternalAdd(nil).Assign(TJclLocationInfoEx(FItems[I]));
- end
- else
- inherited AssignTo(Dest);
- end;
- procedure TJclCustomLocationInfoList.Clear;
- begin
- FItems.Clear;
- end;
- function TJclCustomLocationInfoList.GetAsString: string;
- var
- I: Integer;
- Strings: TStringList;
- begin
- Strings := TStringList.Create;
- try
- for I := 0 to Count - 1 do
- Strings.Add(TJclLocationInfoEx(FItems[I]).AsString);
- Result := Strings.Text;
- finally
- Strings.Free;
- end;
- end;
- function TJclCustomLocationInfoList.GetCount: Integer;
- begin
- Result := FItems.Count;
- end;
- function TJclCustomLocationInfoList.InternalAdd(Addr: Pointer): TJclLocationInfoEx;
- begin
- FItems.Add(FItemClass.Create(Self, Addr));
- Result := TJclLocationInfoEx(FItems.Last);
- end;
- //=== { TJclLocationInfoList } ===============================================
- function TJclLocationInfoList.Add(Addr: Pointer): TJclLocationInfoEx;
- begin
- Result := InternalAdd(Addr);
- end;
- constructor TJclLocationInfoList.Create;
- begin
- inherited Create;
- FOptions := [liloAutoGetAddressInfo, liloAutoGetLocationInfo, liloAutoGetUnitVersionInfo];
- end;
- function TJclLocationInfoList.GetItems(AIndex: Integer): TJclLocationInfoEx;
- begin
- Result := TJclLocationInfoEx(FItems[AIndex]);
- end;
- //=== { TJclDebugInfoSource } ================================================
- constructor TJclDebugInfoSource.Create(AModule: HMODULE);
- var
- MemInfo: TMemoryBasicInformation;
- begin
- FModule := AModule;
- FModuleCodeSize := 0;
- if VirtualQuery(Pointer(TJclAddr(FModule) + ModuleCodeOffset), MemInfo, SizeOf(MemInfo)) = SizeOf(MemInfo) then
- FModuleCodeSize := MemInfo.RegionSize;
- end;
- function TJclDebugInfoSource.GetFileName: TFileName;
- begin
- Result := GetModulePath(FModule);
- end;
- function TJclDebugInfoSource.VAFromAddr(const Addr: Pointer): DWORD;
- begin
- Result := DWORD(TJclAddr(Addr) - TJclAddr(FModule) - ModuleCodeOffset);
- end;
- function TJclDebugInfoSource.AddrFromVA(const VA: DWORD): Pointer;
- begin
- Result := Pointer(TJclAddr(VA) + TJclAddr(FModule) + ModuleCodeOffset);
- end;
- //=== { TJclDebugInfoList } ==================================================
- var
- DebugInfoList: TJclDebugInfoList = nil;
- InfoSourceClassList: TList = nil;
- DebugInfoCritSect: TJclCriticalSection;
- procedure NeedDebugInfoList;
- begin
- if DebugInfoList = nil then
- DebugInfoList := TJclDebugInfoList.Create;
- end;
- function TJclDebugInfoList.CreateDebugInfo(const Module: HMODULE): TJclDebugInfoSource;
- var
- I: Integer;
- begin
- NeedInfoSourceClassList;
- Result := nil;
- for I := 0 to InfoSourceClassList.Count - 1 do
- begin
- Result := TJclDebugInfoSourceClass(InfoSourceClassList.Items[I]).Create(Module);
- try
- if Result.InitializeSource then
- Break
- else
- FreeAndNil(Result);
- except
- Result.Free;
- raise;
- end;
- end;
- end;
- function TJclDebugInfoList.GetItemFromModule(const Module: HMODULE): TJclDebugInfoSource;
- var
- I: Integer;
- TempItem: TJclDebugInfoSource;
- begin
- Result := nil;
- if Module = 0 then
- Exit;
- for I := 0 to Count - 1 do
- begin
- TempItem := Items[I];
- if TempItem.Module = Module then
- begin
- Result := TempItem;
- Break;
- end;
- end;
- if Result = nil then
- begin
- Result := CreateDebugInfo(Module);
- if Result <> nil then
- Add(Result);
- end;
- end;
- function TJclDebugInfoList.GetItems(Index: TJclListSize): TJclDebugInfoSource;
- begin
- Result := TJclDebugInfoSource(Get(Index));
- end;
- function TJclDebugInfoList.GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- var
- Item: TJclDebugInfoSource;
- begin
- ResetMemory(Info, SizeOf(Info));
- Item := ItemFromModule[CachedModuleFromAddr(Addr)];
- if Item <> nil then
- Result := Item.GetLocationInfo(Addr, Info)
- else
- Result := False;
- end;
- class procedure TJclDebugInfoList.NeedInfoSourceClassList;
- begin
- if not Assigned(InfoSourceClassList) then
- begin
- InfoSourceClassList := TList.Create;
- {$IFNDEF DEBUG_NO_BINARY}
- InfoSourceClassList.Add(Pointer(TJclDebugInfoBinary));
- {$ENDIF !DEBUG_NO_BINARY}
- {$IFNDEF DEBUG_NO_TD32}
- {$IFNDEF WINSCP}
- InfoSourceClassList.Add(Pointer(TJclDebugInfoTD32));
- {$ENDIF ~WINSCP}
- {$ENDIF !DEBUG_NO_TD32}
- {$IFNDEF DEBUG_NO_MAP}
- InfoSourceClassList.Add(Pointer(TJclDebugInfoMap));
- {$ENDIF !DEBUG_NO_MAP}
- {$IFNDEF DEBUG_NO_SYMBOLS}
- InfoSourceClassList.Add(Pointer(TJclDebugInfoSymbols));
- {$ENDIF !DEBUG_NO_SYMBOLS}
- {$IFNDEF DEBUG_NO_EXPORTS}
- InfoSourceClassList.Add(Pointer(TJclDebugInfoExports));
- {$ENDIF !DEBUG_NO_EXPORTS}
- end;
- end;
- class procedure TJclDebugInfoList.RegisterDebugInfoSource(
- const InfoSourceClass: TJclDebugInfoSourceClass);
- begin
- NeedInfoSourceClassList;
- InfoSourceClassList.Add(Pointer(InfoSourceClass));
- end;
- class procedure TJclDebugInfoList.RegisterDebugInfoSourceFirst(
- const InfoSourceClass: TJclDebugInfoSourceClass);
- begin
- NeedInfoSourceClassList;
- InfoSourceClassList.Insert(0, Pointer(InfoSourceClass));
- end;
- class procedure TJclDebugInfoList.UnRegisterDebugInfoSource(
- const InfoSourceClass: TJclDebugInfoSourceClass);
- begin
- if Assigned(InfoSourceClassList) then
- InfoSourceClassList.Remove(Pointer(InfoSourceClass));
- end;
- //=== { TJclDebugInfoMap } ===================================================
- destructor TJclDebugInfoMap.Destroy;
- begin
- FreeAndNil(FScanner);
- inherited Destroy;
- end;
- function TJclDebugInfoMap.GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- var
- VA: DWORD;
- begin
- VA := VAFromAddr(Addr);
- with FScanner do
- begin
- Info.UnitName := ModuleNameFromAddr(VA);
- Result := Info.UnitName <> '';
- if Result then
- begin
- Info.Address := Addr;
- Info.ProcedureName := ProcNameFromAddr(VA, Info.OffsetFromProcName);
- Info.LineNumber := LineNumberFromAddr(VA, Info.OffsetFromLineNumber);
- Info.SourceName := SourceNameFromAddr(VA);
- Info.DebugInfo := Self;
- Info.BinaryFileName := FileName;
- end;
- end;
- end;
- function TJclDebugInfoMap.GetAddress(const UnitName, ProcName: string): Pointer;
- var
- VA: DWORD;
- begin
- Result := nil;
- VA := FScanner.VAFromUnitAndProcName(UnitName, ProcName);
- if VA <> 0 then
- Result := AddrFromVA(VA);
- end;
- function TJclDebugInfoMap.InitializeSource: Boolean;
- var
- MapFileName: TFileName;
- begin
- MapFileName := ChangeFileExt(FileName, JclMapFileExtension);
- Result := FileExists(MapFileName);
- if Result then
- FScanner := TJclMapScanner.Create(MapFileName, Module);
- end;
- //=== { TJclDebugInfoBinary } ================================================
- destructor TJclDebugInfoBinary.Destroy;
- begin
- FreeAndNil(FScanner);
- FreeAndNil(FStream);
- inherited Destroy;
- end;
- function TJclDebugInfoBinary.GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- var
- VA: DWORD;
- begin
- VA := VAFromAddr(Addr);
- with FScanner do
- begin
- Info.UnitName := ModuleNameFromAddr(VA);
- Result := Info.UnitName <> '';
- if Result then
- begin
- Info.Address := Addr;
- Info.ProcedureName := ProcNameFromAddr(VA, Info.OffsetFromProcName);
- Info.LineNumber := LineNumberFromAddr(VA, Info.OffsetFromLineNumber);
- Info.SourceName := SourceNameFromAddr(VA);
- Info.DebugInfo := Self;
- Info.BinaryFileName := FileName;
- end;
- end;
- end;
- function TJclDebugInfoBinary.GetAddress(const UnitName, ProcName: string): Pointer;
- var
- VA: DWORD;
- begin
- Result := nil;
- VA := FScanner.VAFromUnitAndProcName(UnitName, ProcName);
- if VA <> 0 then
- Result := AddrFromVA(VA);
- end;
- function TJclDebugInfoBinary.InitializeSource: Boolean;
- var
- JdbgFileName: TFileName;
- VerifyFileName: Boolean;
- begin
- VerifyFileName := False;
- Result := (PeMapImgFindSectionFromModule(Pointer(Module), JclDbgDataResName) <> nil);
- if Result then
- FStream := TJclPeSectionStream.Create(Module, JclDbgDataResName)
- else
- begin
- JdbgFileName := ChangeFileExt(FileName, JclDbgFileExtension);
- Result := FileExists(JdbgFileName);
- if Result then
- begin
- FStream := TJclFileMappingStream.Create(JdbgFileName, fmOpenRead or fmShareDenyWrite);
- VerifyFileName := True;
- end;
- end;
- if Result then
- begin
- FScanner := TJclBinDebugScanner.Create(FStream, True, False);
- Result := FScanner.ValidFormat and
- (not VerifyFileName or FScanner.IsModuleNameValid(FileName));
- end;
- end;
- //=== { TJclDebugInfoExports } ===============================================
- destructor TJclDebugInfoExports.Destroy;
- begin
- FreeAndNil(FImage);
- inherited Destroy;
- end;
- function TJclDebugInfoExports.IsAddressInThisExportedFunction(Addr: PByteArray; FunctionStartAddr: TJclAddr): Boolean;
- begin
- Dec(TJclAddr(Addr), 6);
- Result := False;
- while TJclAddr(Addr) > FunctionStartAddr do
- begin
- if IsBadReadPtr(Addr, 6) then
- Exit;
- if (Addr[0] = $C2) and // ret $xxxx
- (((Addr[3] = $90) and (Addr[4] = $90) and (Addr[5] = $90)) or // nop
- ((Addr[3] = $CC) and (Addr[4] = $CC) and (Addr[5] = $CC))) then // int 3
- Exit;
- if (Addr[0] = $C3) and // ret
- (((Addr[1] = $90) and (Addr[2] = $90) and (Addr[3] = $90)) or // nop
- ((Addr[1] = $CC) and (Addr[2] = $CC) and (Addr[3] = $CC))) then // int 3
- Exit;
- if (Addr[0] = $E9) and // jmp rel-far
- (((Addr[5] = $90) and (Addr[6] = $90) and (Addr[7] = $90)) or // nop
- ((Addr[5] = $CC) and (Addr[6] = $CC) and (Addr[7] = $CC))) then // int 3
- Exit;
- if (Addr[0] = $EB) and // jmp rel-near
- (((Addr[2] = $90) and (Addr[3] = $90) and (Addr[4] = $90)) or // nop
- ((Addr[2] = $CC) and (Addr[3] = $CC) and (Addr[4] = $CC))) then // int 3
- Exit;
- Dec(TJclAddr(Addr));
- end;
- Result := True;
- end;
- function TJclDebugInfoExports.GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- var
- I, BasePos: Integer;
- VA: DWORD;
- Desc: TJclBorUmDescription;
- Unmangled: string;
- RawName: Boolean;
- begin
- Result := False;
- VA := DWORD(TJclAddr(Addr) - TJclAddr(FModule));
- {$IFDEF BORLAND}
- RawName := not FImage.IsPackage;
- {$ENDIF BORLAND}
- {$IFDEF FPC}
- RawName := True;
- {$ENDIF FPC}
- Info.OffsetFromProcName := 0;
- Info.OffsetFromLineNumber := 0;
- Info.BinaryFileName := FileName;
- with FImage.ExportList do
- begin
- SortList(esAddress, False);
- for I := Count - 1 downto 0 do
- if Items[I].Address <= VA then
- begin
- if RawName then
- begin
- Info.ProcedureName := Items[I].Name;
- Info.OffsetFromProcName := VA - Items[I].Address;
- Result := True;
- end
- else
- begin
- case PeBorUnmangleName(Items[I].Name, Unmangled, Desc, BasePos) of
- urOk:
- begin
- Info.UnitName := Copy(Unmangled, 1, BasePos - 2);
- if not (Desc.Kind in [skRTTI, skVTable]) then
- begin
- Info.ProcedureName := Copy(Unmangled, BasePos, Length(Unmangled));
- if smLinkProc in Desc.Modifiers then
- Info.ProcedureName := '@' + Info.ProcedureName;
- Info.OffsetFromProcName := VA - Items[I].Address;
- end;
- Result := True;
- end;
- urNotMangled:
- begin
- Info.ProcedureName := Items[I].Name;
- Info.OffsetFromProcName := VA - Items[I].Address;
- Result := True;
- end;
- end;
- end;
- if Result then
- begin
- Info.Address := Addr;
- Info.DebugInfo := Self;
- { Check if we have a valid address in an exported function. }
- if not IsAddressInThisExportedFunction(Addr, FModule + Items[I].Address) then
- begin
- //Info.UnitName := '[' + AnsiLowerCase(ExtractFileName(GetModulePath(FModule))) + ']'
- {$IFNDEF WINSCP}
- Info.ProcedureName := Format(LoadResString(@RsUnknownFunctionAt), [Info.ProcedureName]);
- {$ELSE}
- Info.ProcedureName := '';
- {$ENDIF ~WINSCP}
- end;
- Break;
- end;
- end;
- end;
- end;
- function TJclDebugInfoExports.GetAddress(const UnitName, ProcName: string): Pointer;
- var
- I, BasePos: Integer;
- Desc: TJclBorUmDescription;
- RawName: Boolean;
- ItemUnitName: string;
- Unmangled: string;
- begin
- Result := nil;
- {$IFDEF BORLAND}
- RawName := not FImage.IsPackage;
- {$ENDIF BORLAND}
- {$IFDEF FPC}
- RawName := True;
- {$ENDIF FPC}
- with FImage.ExportList do
- begin
- // SortList(esAddress, False);
- for I := 0 to Count - 1 do
- begin
- if RawName then
- begin
- ItemUnitName := '';
- Unmangled := Items[I].Name;
- end
- else
- begin
- case PeBorUnmangleName(Items[I].Name, Unmangled, Desc, BasePos) of
- urOk:
- begin
- ItemUnitName := Copy(Unmangled, 1, BasePos - 2);
- if not (Desc.Kind in [skRTTI, skVTable]) then
- begin
- Unmangled := Copy(Unmangled, BasePos, Length(Unmangled));
- if smLinkProc in Desc.Modifiers then
- Unmangled := '@' + Unmangled;
- end;
- end;
- urNotMangled:
- Unmangled := Items[I].Name;
- end;
- end;
- if ((ItemUnitName = '') or (CompareStr(ItemUnitName, UnitName) = 0)) and (CompareStr(Unmangled, ProcName) = 0) then
- begin
- Result := AddrFromVA(Items[I].Address);
- Break;
- end;
- end;
- end;
- end;
- function TJclDebugInfoExports.InitializeSource: Boolean;
- begin
- {$IFDEF BORLAND}
- FImage := TJclPeBorImage.Create(True);
- {$ENDIF BORLAND}
- {$IFDEF FPC}
- FImage := TJclPeImage.Create(True);
- {$ENDIF FPC}
- FImage.AttachLoadedModule(FModule);
- Result := FImage.StatusOK and (FImage.ExportList.Count > 0);
- end;
- {$IFDEF BORLAND}
- {$IFNDEF WINSCP}
- //=== { TJclDebugInfoTD32 } ==================================================
- destructor TJclDebugInfoTD32.Destroy;
- begin
- FreeAndNil(FImage);
- inherited Destroy;
- end;
- function TJclDebugInfoTD32.GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- var
- VA: DWORD;
- begin
- VA := VAFromAddr(Addr);
- Info.UnitName := FImage.TD32Scanner.ModuleNameFromAddr(VA);
- Result := Info.UnitName <> '';
- if Result then
- with Info do
- begin
- Address := Addr;
- ProcedureName := FImage.TD32Scanner.ProcNameFromAddr(VA, OffsetFromProcName);
- LineNumber := FImage.TD32Scanner.LineNumberFromAddr(VA, OffsetFromLineNumber);
- SourceName := FImage.TD32Scanner.SourceNameFromAddr(VA);
- DebugInfo := Self;
- BinaryFileName := FileName;
- end;
- end;
- function TJclDebugInfoTD32.GetAddress(const UnitName, ProcName: string): Pointer;
- var
- VA: DWORD;
- begin
- Result := nil;
- VA := FImage.TD32Scanner.VAFromUnitAndProcName(UnitName, ProcName);
- if VA <> 0 then
- Result := AddrFromVA(VA);
- end;
- function TJclDebugInfoTD32.InitializeSource: Boolean;
- begin
- FImage := TJclPeBorTD32Image.Create(True);
- try
- FImage.AttachLoadedModule(Module);
- Result := FImage.IsTD32DebugPresent;
- except
- Result := False;
- end;
- end;
- procedure TJclDebugInfoTD32.GenerateUnmangledNames;
- begin
- FImage.TD32Scanner.GenerateUnmangledNames;
- end;
- {$ENDIF ~WINSCP}
- {$ENDIF BORLAND}
- //=== { TJclDebugInfoSymbols } ===============================================
- type
- TSymInitializeAFunc = function (hProcess: THandle; UserSearchPath: LPSTR;
- fInvadeProcess: Bool): Bool; stdcall;
- TSymInitializeWFunc = function (hProcess: THandle; UserSearchPath: LPWSTR;
- fInvadeProcess: Bool): Bool; stdcall;
- TSymGetOptionsFunc = function: DWORD; stdcall;
- TSymSetOptionsFunc = function (SymOptions: DWORD): DWORD; stdcall;
- TSymCleanupFunc = function (hProcess: THandle): Bool; stdcall;
- {$IFDEF CPU32}
- TSymGetSymFromAddrAFunc = function (hProcess: THandle; dwAddr: DWORD;
- pdwDisplacement: PDWORD; var Symbol: JclWin32.TImagehlpSymbolA): Bool; stdcall;
- TSymGetSymFromAddrWFunc = function (hProcess: THandle; dwAddr: DWORD;
- pdwDisplacement: PDWORD; var Symbol: JclWin32.TImagehlpSymbolW): Bool; stdcall;
- TSymGetModuleInfoAFunc = function (hProcess: THandle; dwAddr: DWORD;
- var ModuleInfo: JclWin32.TImagehlpModuleA): Bool; stdcall;
- TSymGetModuleInfoWFunc = function (hProcess: THandle; dwAddr: DWORD;
- var ModuleInfo: JclWin32.TImagehlpModuleW): Bool; stdcall;
- TSymLoadModuleFunc = function (hProcess: THandle; hFile: THandle; ImageName,
- ModuleName: LPSTR; BaseOfDll: DWORD; SizeOfDll: DWORD): DWORD; stdcall;
- TSymGetLineFromAddrAFunc = function (hProcess: THandle; dwAddr: DWORD;
- pdwDisplacement: PDWORD; var Line: JclWin32.TImageHlpLineA): Bool; stdcall;
- TSymGetLineFromAddrWFunc = function (hProcess: THandle; dwAddr: DWORD;
- pdwDisplacement: PDWORD; var Line: JclWin32.TImageHlpLineW): Bool; stdcall;
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- TSymGetSymFromAddrAFunc = function (hProcess: THandle; dwAddr: DWORD64;
- pdwDisplacement: PDWORD64; var Symbol: JclWin32.TImagehlpSymbolA64): Bool; stdcall;
- TSymGetSymFromAddrWFunc = function (hProcess: THandle; dwAddr: DWORD64;
- pdwDisplacement: PDWORD64; var Symbol: JclWin32.TImagehlpSymbolW64): Bool; stdcall;
- TSymGetModuleInfoAFunc = function (hProcess: THandle; dwAddr: DWORD64;
- var ModuleInfo: JclWin32.TImagehlpModuleA64): Bool; stdcall;
- TSymGetModuleInfoWFunc = function (hProcess: THandle; dwAddr: DWORD64;
- var ModuleInfo: JclWin32.TImagehlpModuleW64): Bool; stdcall;
- TSymLoadModuleFunc = function (hProcess: THandle; hFile: THandle; ImageName,
- ModuleName: LPSTR; BaseOfDll: DWORD64; SizeOfDll: DWORD): DWORD; stdcall;
- TSymGetLineFromAddrAFunc = function (hProcess: THandle; dwAddr: DWORD64;
- pdwDisplacement: PDWORD; var Line: JclWin32.TImageHlpLineA64): Bool; stdcall;
- TSymGetLineFromAddrWFunc = function (hProcess: THandle; dwAddr: DWORD64;
- pdwDisplacement: PDWORD; var Line: JclWin32.TImageHlpLineW64): Bool; stdcall;
- {$ENDIF CPU64}
- var
- DebugSymbolsInitialized: Boolean = False;
- DebugSymbolsLoadFailed: Boolean = False;
- ImageHlpDllHandle: THandle = 0;
- SymInitializeAFunc: TSymInitializeAFunc = nil;
- SymInitializeWFunc: TSymInitializeWFunc = nil;
- SymGetOptionsFunc: TSymGetOptionsFunc = nil;
- SymSetOptionsFunc: TSymSetOptionsFunc = nil;
- SymCleanupFunc: TSymCleanupFunc = nil;
- SymGetSymFromAddrAFunc: TSymGetSymFromAddrAFunc = nil;
- SymGetSymFromAddrWFunc: TSymGetSymFromAddrWFunc = nil;
- SymGetModuleInfoAFunc: TSymGetModuleInfoAFunc = nil;
- SymGetModuleInfoWFunc: TSymGetModuleInfoWFunc = nil;
- SymLoadModuleFunc: TSymLoadModuleFunc = nil;
- SymGetLineFromAddrAFunc: TSymGetLineFromAddrAFunc = nil;
- SymGetLineFromAddrWFunc: TSymGetLineFromAddrWFunc = nil;
- const
- ImageHlpDllName = 'imagehlp.dll'; // do not localize
- SymInitializeAFuncName = 'SymInitialize'; // do not localize
- SymInitializeWFuncName = 'SymInitializeW'; // do not localize
- SymGetOptionsFuncName = 'SymGetOptions'; // do not localize
- SymSetOptionsFuncName = 'SymSetOptions'; // do not localize
- SymCleanupFuncName = 'SymCleanup'; // do not localize
- {$IFDEF CPU32}
- SymGetSymFromAddrAFuncName = 'SymGetSymFromAddr'; // do not localize
- SymGetSymFromAddrWFuncName = 'SymGetSymFromAddrW'; // do not localize
- SymGetModuleInfoAFuncName = 'SymGetModuleInfo'; // do not localize
- SymGetModuleInfoWFuncName = 'SymGetModuleInfoW'; // do not localize
- SymLoadModuleFuncName = 'SymLoadModule'; // do not localize
- SymGetLineFromAddrAFuncName = 'SymGetLineFromAddr'; // do not localize
- SymGetLineFromAddrWFuncName = 'SymGetLineFromAddrW'; // do not localize
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- SymGetSymFromAddrAFuncName = 'SymGetSymFromAddr64'; // do not localize
- SymGetSymFromAddrWFuncName = 'SymGetSymFromAddrW64'; // do not localize
- SymGetModuleInfoAFuncName = 'SymGetModuleInfo64'; // do not localize
- SymGetModuleInfoWFuncName = 'SymGetModuleInfoW64'; // do not localize
- SymLoadModuleFuncName = 'SymLoadModule64'; // do not localize
- SymGetLineFromAddrAFuncName = 'SymGetLineFromAddr64'; // do not localize
- SymGetLineFromAddrWFuncName = 'SymGetLineFromAddrW64'; // do not localize
- {$ENDIF CPU64}
- function StrRemoveEmptyPaths(const Paths: string): string;
- var
- List: TStrings;
- I: Integer;
- begin
- List := TStringList.Create;
- try
- StrToStrings(Paths, DirSeparator, List, False);
- for I := 0 to List.Count - 1 do
- if Trim(List[I]) = '' then
- List[I] := '';
- Result := StringsToStr(List, DirSeparator, False);
- finally
- List.Free;
- end;
- end;
- class function TJclDebugInfoSymbols.InitializeDebugSymbols: Boolean;
- var
- EnvironmentVarValue, SearchPath: string;
- SymOptions: Cardinal;
- ProcessHandle: THandle;
- begin
- Result := DebugSymbolsInitialized;
- if not DebugSymbolsLoadFailed then
- begin
- Result := LoadDebugFunctions;
- DebugSymbolsLoadFailed := not Result;
- if Result then
- begin
- if JclDebugInfoSymbolPaths <> '' then
- begin
- SearchPath := StrEnsureSuffix(DirSeparator, JclDebugInfoSymbolPaths);
- SearchPath := StrEnsureNoSuffix(DirSeparator, SearchPath + GetCurrentFolder);
- if GetEnvironmentVar(EnvironmentVarNtSymbolPath, EnvironmentVarValue) and (EnvironmentVarValue <> '') then
- SearchPath := StrEnsureNoSuffix(DirSeparator, StrEnsureSuffix(DirSeparator, EnvironmentVarValue) + SearchPath);
- if GetEnvironmentVar(EnvironmentVarAlternateNtSymbolPath, EnvironmentVarValue) and (EnvironmentVarValue <> '') then
- SearchPath := StrEnsureNoSuffix(DirSeparator, StrEnsureSuffix(DirSeparator, EnvironmentVarValue) + SearchPath);
- // DbgHelp.dll crashes when an empty path is specified.
- // This also means that the SearchPath must not end with a DirSeparator. }
- SearchPath := StrRemoveEmptyPaths(SearchPath);
- end
- else
- // Fix crash SymLoadModuleFunc on WinXP SP3 when SearchPath=''
- SearchPath := GetCurrentFolder;
- if IsWinNT then
- // in Windows NT, first argument is a process handle
- ProcessHandle := GetCurrentProcess
- else
- // in Windows 95, 98, ME first argument is a process identifier
- ProcessHandle := GetCurrentProcessId;
- // Debug(WinXPSP3): SymInitializeWFunc==nil
- if Assigned(SymInitializeWFunc) then
- Result := SymInitializeWFunc(ProcessHandle, PWideChar(WideString(SearchPath)), False)
- else
- if Assigned(SymInitializeAFunc) then
- Result := SymInitializeAFunc(ProcessHandle, PAnsiChar(AnsiString(SearchPath)), False)
- else
- Result := False;
- if Result then
- begin
- SymOptions := SymGetOptionsFunc or SYMOPT_DEFERRED_LOADS
- or SYMOPT_FAIL_CRITICAL_ERRORS or SYMOPT_INCLUDE_32BIT_MODULES or SYMOPT_LOAD_LINES;
- SymOptions := SymOptions and (not (SYMOPT_NO_UNQUALIFIED_LOADS or SYMOPT_UNDNAME));
- SymSetOptionsFunc(SymOptions);
- end;
- DebugSymbolsInitialized := Result;
- end
- else
- UnloadDebugFunctions;
- end;
- end;
- class function TJclDebugInfoSymbols.CleanupDebugSymbols: Boolean;
- begin
- Result := True;
- if DebugSymbolsInitialized then
- Result := SymCleanupFunc(GetCurrentProcess);
- UnloadDebugFunctions;
- end;
- function TJclDebugInfoSymbols.GetLocationInfo(const Addr: Pointer;
- out Info: TJclLocationInfo): Boolean;
- const
- SymbolNameLength = 1000;
- {$IFDEF CPU32}
- SymbolSizeA = SizeOf(TImagehlpSymbolA) + SymbolNameLength * SizeOf(AnsiChar);
- SymbolSizeW = SizeOf(TImagehlpSymbolW) + SymbolNameLength * SizeOf(WideChar);
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- SymbolSizeA = SizeOf(TImagehlpSymbolA64) + SymbolNameLength * SizeOf(AnsiChar);
- SymbolSizeW = SizeOf(TImagehlpSymbolW64) + SymbolNameLength * SizeOf(WideChar);
- {$ENDIF CPU64}
- var
- Displacement: DWORD;
- ProcessHandle: THandle;
- {$IFDEF CPU32}
- SymbolA: PImagehlpSymbolA;
- SymbolW: PImagehlpSymbolW;
- LineA: TImageHlpLineA;
- LineW: TImageHlpLineW;
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- SymbolA: PImagehlpSymbolA64;
- SymbolW: PImagehlpSymbolW64;
- LineA: TImageHlpLineA64;
- LineW: TImageHlpLineW64;
- {$ENDIF CPU64}
- begin
- ProcessHandle := GetCurrentProcess;
- if Assigned(SymGetSymFromAddrWFunc) then
- begin
- GetMem(SymbolW, SymbolSizeW);
- try
- ZeroMemory(SymbolW, SymbolSizeW);
- SymbolW^.SizeOfStruct := SizeOf(SymbolW^);
- SymbolW^.MaxNameLength := SymbolNameLength;
- Displacement := 0;
- Result := SymGetSymFromAddrWFunc(ProcessHandle, TJclAddr(Addr), @Displacement, SymbolW^);
- if Result then
- begin
- Info.DebugInfo := Self;
- Info.Address := Addr;
- Info.BinaryFileName := FileName;
- Info.OffsetFromProcName := Displacement;
- JclPeImage.UnDecorateSymbolName(string(PWideChar(@SymbolW^.Name[0])), Info.ProcedureName, UNDNAME_NAME_ONLY or UNDNAME_NO_ARGUMENTS);
- end;
- finally
- FreeMem(SymbolW);
- end;
- end
- else
- if Assigned(SymGetSymFromAddrAFunc) then
- begin
- GetMem(SymbolA, SymbolSizeA);
- try
- ZeroMemory(SymbolA, SymbolSizeA);
- SymbolA^.SizeOfStruct := SizeOf(SymbolA^);
- SymbolA^.MaxNameLength := SymbolNameLength;
- Displacement := 0;
- Result := SymGetSymFromAddrAFunc(ProcessHandle, TJclAddr(Addr), @Displacement, SymbolA^);
- if Result then
- begin
- Info.DebugInfo := Self;
- Info.Address := Addr;
- Info.BinaryFileName := FileName;
- Info.OffsetFromProcName := Displacement;
- JclPeImage.UnDecorateSymbolName(string(PAnsiChar(@SymbolA^.Name[0])), Info.ProcedureName, UNDNAME_NAME_ONLY or UNDNAME_NO_ARGUMENTS);
- end;
- finally
- FreeMem(SymbolA);
- end;
- end
- else
- Result := False;
- // line number is optional
- if Result and Assigned(SymGetLineFromAddrWFunc) then
- begin
- ZeroMemory(@LineW, SizeOf(LineW));
- LineW.SizeOfStruct := SizeOf(LineW);
- Displacement := 0;
- if SymGetLineFromAddrWFunc(ProcessHandle, TJclAddr(Addr), @Displacement, LineW) then
- begin
- Info.LineNumber := LineW.LineNumber;
- Info.UnitName := string(LineW.FileName);
- Info.OffsetFromLineNumber := Displacement;
- end;
- end
- else
- if Result and Assigned(SymGetLineFromAddrAFunc) then
- begin
- ZeroMemory(@LineA, SizeOf(LineA));
- LineA.SizeOfStruct := SizeOf(LineA);
- Displacement := 0;
- if SymGetLineFromAddrAFunc(ProcessHandle, TJclAddr(Addr), @Displacement, LineA) then
- begin
- Info.LineNumber := LineA.LineNumber;
- Info.UnitName := string(LineA.FileName);
- Info.OffsetFromLineNumber := Displacement;
- end;
- end;
- end;
- function TJclDebugInfoSymbols.GetAddress(const UnitName, ProcName: string): Pointer;
- var
- VA: DWORD;
- begin
- Result := nil;
- VA := 0; // FScanner.VAFromUnitAndProcName(UnitName, ProcName);
- if VA <> 0 then
- Result := AddrFromVA(VA);
- end;
- function TJclDebugInfoSymbols.InitializeSource: Boolean;
- var
- ModuleFileName: TFileName;
- {$IFDEF CPU32}
- ModuleInfoA: TImagehlpModuleA;
- ModuleInfoW: TImagehlpModuleW;
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- ModuleInfoA: TImagehlpModuleA64;
- ModuleInfoW: TImagehlpModuleW64;
- {$ENDIF CPU64}
- ProcessHandle: THandle;
- begin
- Result := InitializeDebugSymbols;
- if Result then
- begin
- if IsWinNT then
- // in Windows NT, first argument is a process handle
- ProcessHandle := GetCurrentProcess
- else
- // in Windows 95, 98, ME, first argument is a process identifier
- ProcessHandle := GetCurrentProcessId;
- if Assigned(SymGetModuleInfoWFunc) then
- begin
- ZeroMemory(@ModuleInfoW, SizeOf(ModuleInfoW));
- ModuleInfoW.SizeOfStruct := SizeOf(ModuleInfoW);
- Result := SymGetModuleInfoWFunc(ProcessHandle, Module, ModuleInfoW);
- if not Result then
- begin
- // the symbols for this module are not loaded yet: load the module and query for the symbol again
- ModuleFileName := GetModulePath(Module);
- ZeroMemory(@ModuleInfoW, SizeOf(ModuleInfoW));
- ModuleInfoW.SizeOfStruct := SizeOf(ModuleInfoW);
- // warning: crash on WinXP SP3 when SymInitializeAFunc is called with empty SearchPath
- // OF: possible loss of data
- Result := (SymLoadModuleFunc(ProcessHandle, 0, PAnsiChar(AnsiString(ModuleFileName)), nil, 0, 0) <> 0) and
- SymGetModuleInfoWFunc(ProcessHandle, Module, ModuleInfoW);
- end;
- Result := Result and (ModuleInfoW.BaseOfImage <> 0) and
- not (ModuleInfoW.SymType in [SymNone, SymExport]);
- end
- else
- if Assigned(SymGetModuleInfoAFunc) then
- begin
- ZeroMemory(@ModuleInfoA, SizeOf(ModuleInfoA));
- ModuleInfoA.SizeOfStruct := SizeOf(ModuleInfoA);
- Result := SymGetModuleInfoAFunc(ProcessHandle, Module, ModuleInfoA);
- if not Result then
- begin
- // the symbols for this module are not loaded yet: load the module and query for the symbol again
- ModuleFileName := GetModulePath(Module);
- ZeroMemory(@ModuleInfoA, SizeOf(ModuleInfoA));
- ModuleInfoA.SizeOfStruct := SizeOf(ModuleInfoA);
- // warning: crash on WinXP SP3 when SymInitializeAFunc is called with empty SearchPath
- // OF: possible loss of data
- Result := (SymLoadModuleFunc(ProcessHandle, 0, PAnsiChar(AnsiString(ModuleFileName)), nil, 0, 0) <> 0) and
- SymGetModuleInfoAFunc(ProcessHandle, Module, ModuleInfoA);
- end;
- Result := Result and (ModuleInfoA.BaseOfImage <> 0) and
- not (ModuleInfoA.SymType in [SymNone, SymExport]);
- end
- else
- Result := False;
- end;
- end;
- class function TJclDebugInfoSymbols.LoadDebugFunctions: Boolean;
- begin
- ImageHlpDllHandle := SafeLoadLibrary(ImageHlpDllName);
- if ImageHlpDllHandle <> 0 then
- begin
- SymInitializeAFunc := GetProcAddress(ImageHlpDllHandle, SymInitializeAFuncName);
- SymInitializeWFunc := GetProcAddress(ImageHlpDllHandle, SymInitializeWFuncName);
- SymGetOptionsFunc := GetProcAddress(ImageHlpDllHandle, SymGetOptionsFuncName);
- SymSetOptionsFunc := GetProcAddress(ImageHlpDllHandle, SymSetOptionsFuncName);
- SymCleanupFunc := GetProcAddress(ImageHlpDllHandle, SymCleanupFuncName);
- SymGetSymFromAddrAFunc := GetProcAddress(ImageHlpDllHandle, SymGetSymFromAddrAFuncName);
- SymGetSymFromAddrWFunc := GetProcAddress(ImageHlpDllHandle, SymGetSymFromAddrWFuncName);
- SymGetModuleInfoAFunc := GetProcAddress(ImageHlpDllHandle, SymGetModuleInfoAFuncName);
- SymGetModuleInfoWFunc := GetProcAddress(ImageHlpDllHandle, SymGetModuleInfoWFuncName);
- SymLoadModuleFunc := GetProcAddress(ImageHlpDllHandle, SymLoadModuleFuncName);
- SymGetLineFromAddrAFunc := GetProcAddress(ImageHlpDllHandle, SymGetLineFromAddrAFuncName);
- SymGetLineFromAddrWFunc := GetProcAddress(ImageHlpDllHandle, SymGetLineFromAddrWFuncName);
- end;
- // SymGetLineFromAddrFunc is optional
- Result := (ImageHlpDllHandle <> 0) and
- Assigned(SymGetOptionsFunc) and Assigned(SymSetOptionsFunc) and
- Assigned(SymCleanupFunc) and Assigned(SymLoadModuleFunc) and
- (Assigned(SymInitializeAFunc) or Assigned(SymInitializeWFunc)) and
- (Assigned(SymGetSymFromAddrAFunc) or Assigned(SymGetSymFromAddrWFunc)) and
- (Assigned(SymGetModuleInfoAFunc) or Assigned(SymGetModuleInfoWFunc));
- end;
- class function TJclDebugInfoSymbols.UnloadDebugFunctions: Boolean;
- begin
- Result := ImageHlpDllHandle <> 0;
- if Result then
- FreeLibrary(ImageHlpDllHandle);
- ImageHlpDllHandle := 0;
- SymInitializeAFunc := nil;
- SymInitializeWFunc := nil;
- SymGetOptionsFunc := nil;
- SymSetOptionsFunc := nil;
- SymCleanupFunc := nil;
- SymGetSymFromAddrAFunc := nil;
- SymGetSymFromAddrWFunc := nil;
- SymGetModuleInfoAFunc := nil;
- SymGetModuleInfoWFunc := nil;
- SymLoadModuleFunc := nil;
- SymGetLineFromAddrAFunc := nil;
- SymGetLineFromAddrWFunc := nil;
- end;
- //=== Source location functions ==============================================
- {$STACKFRAMES ON}
- function Caller(Level: Integer; FastStackWalk: Boolean): Pointer;
- var
- TopOfStack: TJclAddr;
- BaseOfStack: TJclAddr;
- StackFrame: PStackFrame;
- begin
- Result := nil;
- try
- if FastStackWalk then
- begin
- StackFrame := GetFramePointer;
- BaseOfStack := TJclAddr(StackFrame) - 1;
- TopOfStack := GetStackTop;
- while (BaseOfStack < TJclAddr(StackFrame)) and (TJclAddr(StackFrame) < TopOfStack) do
- begin
- if Level = 0 then
- begin
- Result := Pointer(StackFrame^.CallerAddr - 1);
- Break;
- end;
- StackFrame := PStackFrame(StackFrame^.CallerFrame);
- Dec(Level);
- end;
- end
- else
- with TJclStackInfoList.Create(False, 1, nil, False, nil, nil) do
- try
- if Level < Count then
- Result := Items[Level].CallerAddr;
- finally
- Free;
- end;
- except
- Result := nil;
- end;
- end;
- {$IFNDEF STACKFRAMES_ON}
- {$STACKFRAMES OFF}
- {$ENDIF ~STACKFRAMES_ON}
- procedure BeginGetLocationInfoCache;
- begin
- BeginModuleFromAddrCache;
- end;
- procedure EndGetLocationInfoCache;
- begin
- EndModuleFromAddrCache;
- end;
- function GetLocationInfo(const Addr: Pointer): TJclLocationInfo;
- begin
- try
- DebugInfoCritSect.Enter;
- try
- NeedDebugInfoList;
- DebugInfoList.GetLocationInfo(Addr, Result)
- finally
- DebugInfoCritSect.Leave;
- end;
- except
- Finalize(Result);
- ResetMemory(Result, SizeOf(Result));
- end;
- end;
- function GetLocationInfo(const Addr: Pointer; out Info: TJclLocationInfo): Boolean;
- begin
- try
- DebugInfoCritSect.Enter;
- try
- NeedDebugInfoList;
- Result := DebugInfoList.GetLocationInfo(Addr, Info);
- finally
- DebugInfoCritSect.Leave;
- end;
- except
- Result := False;
- end;
- end;
- function GetLocationInfoStr(const Addr: Pointer; IncludeModuleName, IncludeAddressOffset,
- IncludeStartProcLineOffset: Boolean; IncludeVAddress: Boolean): string;
- var
- Info, StartProcInfo: TJclLocationInfo;
- OffsetStr, StartProcOffsetStr, FixedProcedureName, UnitNameWithoutUnitscope: string;
- Module: HMODULE;
- {$IFDEF WINSCP}
- MainModule: HMODULE;
- ModuleName: string;
- ModulePosition: Integer;
- {$ENDIF ~WINSCP}
- begin
- OffsetStr := '';
- if GetLocationInfo(Addr, Info) then
- with Info do
- begin
- FixedProcedureName := ProcedureName;
- if Pos(UnitName + '.', FixedProcedureName) = 1 then
- FixedProcedureName := Copy(FixedProcedureName, Length(UnitName) + 2, Length(FixedProcedureName) - Length(UnitName) - 1)
- else
- if Pos('.', UnitName) > 1 then
- begin
- UnitNameWithoutUnitscope := UnitName;
- Delete(UnitNameWithoutUnitscope, 1, Pos('.', UnitNameWithoutUnitscope));
- if Pos(StrLower(UnitNameWithoutUnitscope) + '.', StrLower(FixedProcedureName)) = 1 then
- FixedProcedureName := Copy(FixedProcedureName, Length(UnitNameWithoutUnitscope) + 2, Length(FixedProcedureName) - Length(UnitNameWithoutUnitscope) - 1);
- end;
- if LineNumber > 0 then
- begin
- if IncludeStartProcLineOffset and GetLocationInfo(Pointer(TJclAddr(Info.Address) -
- Cardinal(Info.OffsetFromProcName)), StartProcInfo) and (StartProcInfo.LineNumber > 0) then
- StartProcOffsetStr := Format(' + %d', [LineNumber - StartProcInfo.LineNumber])
- else
- StartProcOffsetStr := '';
- if IncludeAddressOffset then
- begin
- if OffsetFromLineNumber >= 0 then
- OffsetStr := Format(' + $%x', [OffsetFromLineNumber])
- else
- OffsetStr := Format(' - $%x', [-OffsetFromLineNumber])
- end;
- {$IFDEF WINSCP}
- Result := Format('[%p] %s (Line %u, "%s"%s)%s', [Addr, FixedProcedureName, LineNumber,
- SourceName, StartProcOffsetStr, OffsetStr]);
- {$ELSE}
- Result := Format('[%p] %s.%s (Line %u, "%s"%s)%s', [Addr, UnitName, FixedProcedureName, LineNumber,
- SourceName, StartProcOffsetStr, OffsetStr]);
- {$ENDIF}
- end
- else
- begin
- if IncludeAddressOffset then
- OffsetStr := Format(' + $%x', [OffsetFromProcName]);
- {$IFNDEF WINSCP}
- if UnitName <> '' then
- Result := Format('[%p] %s.%s%s', [Addr, UnitName, FixedProcedureName, OffsetStr])
- else
- {$ENDIF}
- Result := Format('[%p] %s%s', [Addr, FixedProcedureName, OffsetStr]);
- end;
- end
- else
- begin
- Result := Format('[%p]', [Addr]);
- IncludeVAddress := True;
- end;
- if IncludeVAddress or IncludeModuleName then
- begin
- Module := ModuleFromAddr(Addr);
- if IncludeVAddress then
- begin
- {$OVERFLOWCHECKS OFF} // Mantis #6104
- OffsetStr := Format('(%p) ', [Pointer(TJclAddr(Addr) - TJclAddr(Module) - ModuleCodeOffset)]);
- {$IFDEF OVERFLOWCHECKS_ON}
- {$OVERFLOWCHECKS ON}
- {$ENDIF OVERFLOWCHECKS_OFF}
- Result := OffsetStr + Result;
- end;
- if IncludeModuleName then
- {$IFDEF WINSCP}
- begin
- MainModule := GetModuleHandle(nil);
- if MainModule <> Module then
- begin
- ModuleName := ExtractFileName(GetModulePath(Module));
- ModulePosition := 12 {$IFDEF CPU64}+8{$ENDIF};
- if IncludeVAddress then
- ModulePosition := 2 * (ModulePosition - 1) + 1;
- if ModulePosition < Length(Result) then
- ModuleName := ModuleName + '.';
- Insert(ModuleName, Result, ModulePosition);
- end;
- end;
- {$ELSE}
- Insert(Format('{%-12s}', [ExtractFileName(GetModulePath(Module))]), Result, 11 {$IFDEF CPU64}+8{$ENDIF});
- {$ENDIF ~WINSCP}
- end;
- end;
- function DebugInfoAvailable(const Module: HMODULE): Boolean;
- begin
- DebugInfoCritSect.Enter;
- try
- NeedDebugInfoList;
- Result := (DebugInfoList.ItemFromModule[Module] <> nil);
- finally
- DebugInfoCritSect.Leave;
- end;
- end;
- procedure ClearLocationData;
- begin
- DebugInfoCritSect.Enter;
- try
- if DebugInfoList <> nil then
- DebugInfoList.Clear;
- finally
- DebugInfoCritSect.Leave;
- end;
- end;
- {$STACKFRAMES ON}
- function FileByLevel(const Level: Integer): string;
- begin
- Result := GetLocationInfo(Caller(Level + 1)).SourceName;
- end;
- function ModuleByLevel(const Level: Integer): string;
- begin
- Result := GetLocationInfo(Caller(Level + 1)).UnitName;
- end;
- function ProcByLevel(const Level: Integer; OnlyProcedureName: boolean): string;
- begin
- Result := GetLocationInfo(Caller(Level + 1)).ProcedureName;
- if OnlyProcedureName = true then
- begin
- if StrILastPos('.', Result) > 0 then
- Result :=StrRestOf(Result, StrILastPos('.', Result)+1);
- end;
- end;
- function LineByLevel(const Level: Integer): Integer;
- begin
- Result := GetLocationInfo(Caller(Level + 1)).LineNumber;
- end;
- function MapByLevel(const Level: Integer; var File_, Module_, Proc_: string;
- var Line_: Integer): Boolean;
- begin
- Result := MapOfAddr(Caller(Level + 1), File_, Module_, Proc_, Line_);
- end;
- function ExtractClassName(const ProcedureName: string): string;
- var
- D: Integer;
- begin
- D := Pos('.', ProcedureName);
- if D < 2 then
- Result := ''
- else
- Result := Copy(ProcedureName, 1, D - 1);
- end;
- function ExtractMethodName(const ProcedureName: string): string;
- begin
- Result := Copy(ProcedureName, Pos('.', ProcedureName) + 1, Length(ProcedureName));
- end;
- function __FILE__(const Level: Integer): string;
- begin
- Result := FileByLevel(Level + 1);
- end;
- function __MODULE__(const Level: Integer): string;
- begin
- Result := ModuleByLevel(Level + 1);
- end;
- function __PROC__(const Level: Integer): string;
- begin
- Result := ProcByLevel(Level + 1);
- end;
- function __LINE__(const Level: Integer): Integer;
- begin
- Result := LineByLevel(Level + 1);
- end;
- function __MAP__(const Level: Integer; var _File, _Module, _Proc: string; var _Line: Integer): Boolean;
- begin
- Result := MapByLevel(Level + 1, _File, _Module, _Proc, _Line);
- end;
- {$IFNDEF STACKFRAMES_ON}
- {$STACKFRAMES OFF}
- {$ENDIF ~STACKFRAMES_ON}
- function FileOfAddr(const Addr: Pointer): string;
- begin
- Result := GetLocationInfo(Addr).SourceName;
- end;
- function ModuleOfAddr(const Addr: Pointer): string;
- begin
- Result := GetLocationInfo(Addr).UnitName;
- end;
- function ProcOfAddr(const Addr: Pointer): string;
- begin
- Result := GetLocationInfo(Addr).ProcedureName;
- end;
- function LineOfAddr(const Addr: Pointer): Integer;
- begin
- Result := GetLocationInfo(Addr).LineNumber;
- end;
- function MapOfAddr(const Addr: Pointer; var File_, Module_, Proc_: string;
- var Line_: Integer): Boolean;
- var
- LocInfo: TJclLocationInfo;
- begin
- NeedDebugInfoList;
- Result := DebugInfoList.GetLocationInfo(Addr, LocInfo);
- if Result then
- begin
- File_ := LocInfo.SourceName;
- Module_ := LocInfo.UnitName;
- Proc_ := LocInfo.ProcedureName;
- Line_ := LocInfo.LineNumber;
- end;
- end;
- function __FILE_OF_ADDR__(const Addr: Pointer): string;
- begin
- Result := FileOfAddr(Addr);
- end;
- function __MODULE_OF_ADDR__(const Addr: Pointer): string;
- begin
- Result := ModuleOfAddr(Addr);
- end;
- function __PROC_OF_ADDR__(const Addr: Pointer): string;
- begin
- Result := ProcOfAddr(Addr);
- end;
- function __LINE_OF_ADDR__(const Addr: Pointer): Integer;
- begin
- Result := LineOfAddr(Addr);
- end;
- function __MAP_OF_ADDR__(const Addr: Pointer; var _File, _Module, _Proc: string;
- var _Line: Integer): Boolean;
- begin
- Result := MapOfAddr(Addr, _File, _Module, _Proc, _Line);
- end;
- //=== { TJclStackBaseList } ==================================================
- constructor TJclStackBaseList.Create;
- begin
- inherited Create(True);
- FThreadID := GetCurrentThreadId;
- FTimeStamp := Now;
- end;
- destructor TJclStackBaseList.Destroy;
- begin
- if Assigned(FOnDestroy) then
- FOnDestroy(Self);
- inherited Destroy;
- end;
- //=== { TJclGlobalStackList } ================================================
- type
- TJclStackBaseListClass = class of TJclStackBaseList;
- TJclGlobalStackList = class(TThreadList)
- private
- FLockedTID: DWORD;
- FTIDLocked: Boolean;
- function GetExceptStackInfo(TID: DWORD): TJclStackInfoList;
- function GetLastExceptFrameList(TID: DWORD): TJclExceptFrameList;
- procedure ItemDestroyed(Sender: TObject);
- public
- destructor Destroy; override;
- procedure AddObject(AObject: TJclStackBaseList);
- procedure Clear;
- procedure LockThreadID(TID: DWORD);
- procedure UnlockThreadID;
- function FindObject(TID: DWORD; AClass: TJclStackBaseListClass): TJclStackBaseList;
- property ExceptStackInfo[TID: DWORD]: TJclStackInfoList read GetExceptStackInfo;
- property LastExceptFrameList[TID: DWORD]: TJclExceptFrameList read GetLastExceptFrameList;
- end;
- var
- GlobalStackList: TJclGlobalStackList;
- destructor TJclGlobalStackList.Destroy;
- begin
- with LockList do
- try
- while Count > 0 do
- TObject(Items[0]).Free;
- finally
- UnlockList;
- end;
- inherited Destroy;
- end;
- procedure TJclGlobalStackList.AddObject(AObject: TJclStackBaseList);
- var
- ReplacedObj: TObject;
- begin
- AObject.FOnDestroy := ItemDestroyed;
- with LockList do
- try
- ReplacedObj := FindObject(AObject.ThreadID, TJclStackBaseListClass(AObject.ClassType));
- if ReplacedObj <> nil then
- begin
- Remove(ReplacedObj);
- ReplacedObj.Free;
- end;
- Add(AObject);
- finally
- UnlockList;
- end;
- end;
- procedure TJclGlobalStackList.Clear;
- begin
- with LockList do
- try
- while Count > 0 do
- TObject(Items[0]).Free;
- { The following call to Clear seems to be useless, but it deallocates memory
- by setting the lists capacity back to zero. For the runtime memory leak check
- within DUnit it is important that the allocated memory before and after the
- test is equal. }
- Clear; // do not remove
- finally
- UnlockList;
- end;
- end;
- function TJclGlobalStackList.FindObject(TID: DWORD; AClass: TJclStackBaseListClass): TJclStackBaseList;
- var
- I: Integer;
- Item: TJclStackBaseList;
- begin
- Result := nil;
- with LockList do
- try
- if FTIDLocked and (GetCurrentThreadId = MainThreadID) then
- TID := FLockedTID;
- for I := 0 to Count - 1 do
- begin
- Item := Items[I];
- if (Item.ThreadID = TID) and (Item is AClass) then
- begin
- Result := Item;
- Break;
- end;
- end;
- finally
- UnlockList;
- end;
- end;
- function TJclGlobalStackList.GetExceptStackInfo(TID: DWORD): TJclStackInfoList;
- begin
- Result := TJclStackInfoList(FindObject(TID, TJclStackInfoList));
- end;
- function TJclGlobalStackList.GetLastExceptFrameList(TID: DWORD): TJclExceptFrameList;
- begin
- Result := TJclExceptFrameList(FindObject(TID, TJclExceptFrameList));
- end;
- procedure TJclGlobalStackList.ItemDestroyed(Sender: TObject);
- begin
- with LockList do
- try
- Remove(Sender);
- finally
- UnlockList;
- end;
- end;
- procedure TJclGlobalStackList.LockThreadID(TID: DWORD);
- begin
- with LockList do
- try
- if GetCurrentThreadId = MainThreadID then
- begin
- FTIDLocked := True;
- FLockedTID := TID;
- end
- else
- FTIDLocked := False;
- finally
- UnlockList;
- end;
- end;
- procedure TJclGlobalStackList.UnlockThreadID;
- begin
- with LockList do
- try
- FTIDLocked := False;
- finally
- UnlockList;
- end;
- end;
- //=== { TJclGlobalModulesList } ==============================================
- type
- TJclGlobalModulesList = class(TObject)
- private
- FAddedModules: TStringList;
- FHookedModules: TJclModuleArray;
- FLock: TJclCriticalSection;
- FModulesList: TJclModuleInfoList;
- public
- constructor Create;
- destructor Destroy; override;
- procedure AddModule(const ModuleName: string);
- function CreateModulesList: TJclModuleInfoList;
- procedure FreeModulesList(var ModulesList: TJclModuleInfoList);
- function ValidateAddress(Addr: Pointer): Boolean;
- end;
- var
- GlobalModulesList: TJclGlobalModulesList;
- constructor TJclGlobalModulesList.Create;
- begin
- FLock := TJclCriticalSection.Create;
- end;
- destructor TJclGlobalModulesList.Destroy;
- begin
- FreeAndNil(FLock);
- // Keep FModulesList alive if there are still TJclStackInfoLists referencing it. The
- // last JclStackInfoList will destroy it through FreeModulesList.
- if (FModulesList <> nil) and (FModulesList.FRefCount = 0) then
- FreeAndNil(FModulesList);
- FreeAndNil(FAddedModules);
- inherited Destroy;
- end;
- procedure TJclGlobalModulesList.AddModule(const ModuleName: string);
- var
- IsMultiThreaded: Boolean;
- begin
- IsMultiThreaded := IsMultiThread;
- if IsMultiThreaded then
- FLock.Enter;
- try
- if not Assigned(FAddedModules) then
- begin
- FAddedModules := TStringList.Create;
- FAddedModules.Sorted := True;
- FAddedModules.Duplicates := dupIgnore;
- end;
- FAddedModules.Add(ModuleName);
- finally
- if IsMultiThreaded then
- FLock.Leave;
- end;
- end;
- function TJclGlobalModulesList.CreateModulesList: TJclModuleInfoList;
- var
- I: Integer;
- SystemModulesOnly: Boolean;
- IsMultiThreaded: Boolean;
- AddedModuleHandle: HMODULE;
- begin
- IsMultiThreaded := IsMultiThread;
- if IsMultiThreaded then
- FLock.Enter;
- try
- if FModulesList = nil then
- begin
- SystemModulesOnly := not (stAllModules in JclStackTrackingOptions);
- Result := TJclModuleInfoList.Create(False, SystemModulesOnly);
- // Add known Borland modules collected by DLL exception hooking code
- if SystemModulesOnly and JclHookedExceptModulesList(FHookedModules) then
- for I := Low(FHookedModules) to High(FHookedModules) do
- Result.AddModule(FHookedModules[I], True);
- if Assigned(FAddedModules) then
- for I := 0 to FAddedModules.Count - 1 do
- begin
- AddedModuleHandle := GetModuleHandle(PChar(FAddedModules[I]));
- if (AddedModuleHandle <> 0) and
- not Assigned(Result.ModuleFromAddress[Pointer(AddedModuleHandle)]) then
- Result.AddModule(AddedModuleHandle, True);
- end;
- if stStaticModuleList in JclStackTrackingOptions then
- FModulesList := Result;
- end
- else
- Result := FModulesList;
- finally
- if IsMultiThreaded then
- FLock.Leave;
- end;
- // RefCount the "global" FModulesList so that if GlobalModulesList is destroyed we can keep
- // the FModulesList alive and let it be destroyed by the last TJclStackInfoList.
- if Result = FModulesList then
- InterlockedIncrement(FModulesList.FRefCount);
- end;
- procedure TJclGlobalModulesList.FreeModulesList(var ModulesList: TJclModuleInfoList);
- var
- IsMultiThreaded: Boolean;
- begin
- if Self <> nil then // happens when finalization already ran but a TJclStackInfoList is still alive
- begin
- if FModulesList <> ModulesList then
- begin
- IsMultiThreaded := IsMultiThread;
- if IsMultiThreaded then
- FLock.Enter;
- try
- FreeAndNil(ModulesList);
- finally
- if IsMultiThreaded then
- FLock.Leave;
- end;
- end
- else if FModulesList <> nil then
- InterlockedDecrement(FModulesList.FRefCount);
- end
- else
- if InterlockedDecrement(ModulesList.FRefCount) = 0 then
- FreeAndNil(ModulesList);
- end;
- function TJclGlobalModulesList.ValidateAddress(Addr: Pointer): Boolean;
- var
- TempList: TJclModuleInfoList;
- begin
- TempList := CreateModulesList;
- try
- Result := TempList.IsValidModuleAddress(Addr);
- finally
- FreeModulesList(TempList);
- end;
- end;
- function JclValidateModuleAddress(Addr: Pointer): Boolean;
- begin
- Result := GlobalModulesList.ValidateAddress(Addr);
- end;
- //=== Stack info routines ====================================================
- {$STACKFRAMES OFF}
- function ValidCodeAddr(CodeAddr: DWORD; ModuleList: TJclModuleInfoList): Boolean;
- begin
- if stAllModules in JclStackTrackingOptions then
- Result := ModuleList.IsValidModuleAddress(Pointer(CodeAddr))
- else
- Result := ModuleList.IsSystemModuleAddress(Pointer(CodeAddr));
- end;
- procedure CorrectExceptStackListTop(List: TJclStackInfoList; SkipFirstItem: Boolean);
- var
- TopItem, I, FoundPos: Integer;
- begin
- FoundPos := -1;
- if SkipFirstItem then
- TopItem := 1
- else
- TopItem := 0;
- with List do
- begin
- for I := Count - 1 downto TopItem do
- if JclBelongsHookedCode(Items[I].CallerAddr) then
- begin
- FoundPos := I;
- Break;
- end;
- if FoundPos <> -1 then
- for I := FoundPos downto TopItem do
- Delete(I);
- end;
- end;
- {$STACKFRAMES ON}
- procedure DoExceptionStackTrace(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean;
- BaseOfStack: Pointer);
- var
- IgnoreLevels: Integer;
- FirstCaller: Pointer;
- RawMode: Boolean;
- Delayed: Boolean;
- begin
- RawMode := stRawMode in JclStackTrackingOptions;
- Delayed := stDelayedTrace in JclStackTrackingOptions;
- if BaseOfStack = nil then
- begin
- BaseOfStack := GetFramePointer;
- IgnoreLevels := 1;
- end
- else
- IgnoreLevels := -1; // because of the "IgnoreLevels + 1" in TJclStackInfoList.StoreToList()
- if OSException then
- begin
- if IgnoreLevels = -1 then
- IgnoreLevels := 0
- else
- Inc(IgnoreLevels); // => HandleAnyException
- FirstCaller := ExceptAddr;
- end
- else
- FirstCaller := nil;
- JclCreateStackList(RawMode, IgnoreLevels, FirstCaller, Delayed, BaseOfStack).CorrectOnAccess(OSException);
- end;
- function JclLastExceptStackList: TJclStackInfoList;
- begin
- Result := GlobalStackList.ExceptStackInfo[GetCurrentThreadID];
- end;
- function JclLastExceptStackListToStrings(Strings: TStrings; IncludeModuleName, IncludeAddressOffset,
- IncludeStartProcLineOffset, IncludeVAddress: Boolean): Boolean;
- var
- List: TJclStackInfoList;
- begin
- List := JclLastExceptStackList;
- Result := Assigned(List);
- if Result then
- List.AddToStrings(Strings, IncludeModuleName, IncludeAddressOffset, IncludeStartProcLineOffset,
- IncludeVAddress);
- end;
- function JclGetExceptStackList(ThreadID: DWORD): TJclStackInfoList;
- begin
- Result := GlobalStackList.ExceptStackInfo[ThreadID];
- end;
- function JclGetExceptStackListToStrings(ThreadID: DWORD; Strings: TStrings;
- IncludeModuleName: Boolean = False; IncludeAddressOffset: Boolean = False;
- IncludeStartProcLineOffset: Boolean = False; IncludeVAddress: Boolean = False): Boolean;
- var
- List: TJclStackInfoList;
- begin
- List := JclGetExceptStackList(ThreadID);
- Result := Assigned(List);
- if Result then
- List.AddToStrings(Strings, IncludeModuleName, IncludeAddressOffset, IncludeStartProcLineOffset,
- IncludeVAddress);
- end;
- procedure JclClearGlobalStackData;
- begin
- GlobalStackList.Clear;
- end;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer): TJclStackInfoList;
- begin
- Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, False, nil, nil);
- GlobalStackList.AddObject(Result);
- end;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer;
- DelayedTrace: Boolean): TJclStackInfoList;
- begin
- Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, DelayedTrace, nil, nil);
- GlobalStackList.AddObject(Result);
- end;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer;
- DelayedTrace: Boolean; BaseOfStack: Pointer): TJclStackInfoList;
- begin
- Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, DelayedTrace, BaseOfStack, nil);
- GlobalStackList.AddObject(Result);
- end;
- function JclCreateStackList(Raw: Boolean; AIgnoreLevels: Integer; FirstCaller: Pointer;
- DelayedTrace: Boolean; BaseOfStack, TopOfStack: Pointer): TJclStackInfoList;
- begin
- Result := TJclStackInfoList.Create(Raw, AIgnoreLevels, FirstCaller, DelayedTrace, BaseOfStack, TopOfStack);
- GlobalStackList.AddObject(Result);
- end;
- function GetThreadTopOfStack(ThreadHandle: THandle): TJclAddr;
- var
- TBI: THREAD_BASIC_INFORMATION;
- ReturnedLength: ULONG;
- begin
- {$IFNDEF COMPILER37_UP}
- Result := 0;
- {$ENDIF ~COMPILER37_UP}
- ReturnedLength := 0;
- if (NtQueryInformationThread(ThreadHandle, ThreadBasicInformation, @TBI, SizeOf(TBI), @ReturnedLength) < $80000000) and
- (ReturnedLength = SizeOf(TBI)) then
- {$IFDEF CPU32}
- Result := TJclAddr(PNT_TIB32(TBI.TebBaseAddress)^.StackBase)
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- Result := TJclAddr(PNT_TIB64(TBI.TebBaseAddress)^.StackBase)
- {$ENDIF CPU64}
- else
- RaiseLastOSError;
- end;
- function JclCreateThreadStackTrace(Raw: Boolean; const ThreadHandle: THandle): TJclStackInfoList;
- var
- ContextMemory: Pointer;
- AlignedContext: PContext;
- begin
- Result := nil;
- ContextMemory := AllocMem(SizeOf(TContext) + 15);
- try
- if (TJclAddr(ContextMemory) and 15) <> 0 then
- // PAnsiChar: TJclAddr is signed and would cause an int overflow for half the address space
- AlignedContext := PContext(TJclAddr(PAnsiChar(ContextMemory) + 16) and -16)
- else
- AlignedContext := ContextMemory;
- AlignedContext^.ContextFlags := CONTEXT_FULL;
- {$IFDEF CPU32}
- if GetThreadContext(ThreadHandle, AlignedContext^) then
- begin
- Result := JclCreateStackList(Raw, -1, Pointer(AlignedContext^.Eip), False, Pointer(AlignedContext^.Ebp),
- Pointer(GetThreadTopOfStack(ThreadHandle)));
- end;
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- if GetThreadContext(ThreadHandle, AlignedContext^) then
- Result := JclCreateStackList(Raw, -1, Pointer(AlignedContext^.Rip), False, Pointer(AlignedContext^.Rbp),
- Pointer(GetThreadTopOfStack(ThreadHandle)));
- {$ENDIF CPU64}
- finally
- FreeMem(ContextMemory);
- end;
- end;
- function JclCreateThreadStackTraceFromID(Raw: Boolean; ThreadID: DWORD): TJclStackInfoList;
- type
- TOpenThreadFunc = function(DesiredAccess: DWORD; InheritHandle: BOOL; ThreadID: DWORD): THandle; stdcall;
- const
- THREAD_GET_CONTEXT = $0008;
- THREAD_QUERY_INFORMATION = $0040;
- var
- Kernel32Lib, ThreadHandle: THandle;
- OpenThreadFunc: TOpenThreadFunc;
- begin
- Result := nil;
- Kernel32Lib := GetModuleHandle(kernel32);
- if Kernel32Lib <> 0 then
- begin
- // OpenThread only exists since Windows ME
- OpenThreadFunc := GetProcAddress(Kernel32Lib, 'OpenThread');
- if Assigned(OpenThreadFunc) then
- begin
- ThreadHandle := OpenThreadFunc(THREAD_GET_CONTEXT or THREAD_QUERY_INFORMATION, False, ThreadID);
- if ThreadHandle <> 0 then
- try
- Result := JclCreateThreadStackTrace(Raw, ThreadHandle);
- finally
- CloseHandle(ThreadHandle);
- end;
- end;
- end;
- end;
- //=== { TJclStackInfoItem } ==================================================
- function TJclStackInfoItem.GetCallerAddr: Pointer;
- begin
- Result := Pointer(FStackInfo.CallerAddr);
- end;
- function TJclStackInfoItem.GetLogicalAddress: TJclAddr;
- begin
- Result := FStackInfo.CallerAddr - TJclAddr(ModuleFromAddr(CallerAddr));
- end;
- //=== { TJclStackInfoList } ==================================================
- constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer);
- begin
- Create(ARaw, AIgnoreLevels, AFirstCaller, False, nil, nil);
- end;
- constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer; ADelayedTrace: Boolean);
- begin
- Create(ARaw, AIgnoreLevels, AFirstCaller, ADelayedTrace, nil, nil);
- end;
- constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack: Pointer);
- begin
- Create(ARaw, AIgnoreLevels, AFirstCaller, ADelayedTrace, ABaseOfStack, nil);
- end;
- constructor TJclStackInfoList.Create(ARaw: Boolean; AIgnoreLevels: Integer;
- AFirstCaller: Pointer; ADelayedTrace: Boolean; ABaseOfStack, ATopOfStack: Pointer);
- var
- Item: TJclStackInfoItem;
- begin
- inherited Create;
- InterlockedIncrement(GlobalStackListLiveCount);
- FIgnoreLevels := AIgnoreLevels;
- FDelayedTrace := ADelayedTrace;
- FRaw := ARaw;
- BaseOfStack := TJclAddr(ABaseOfStack);
- FStackOffset := 0;
- FFramePointer := ABaseOfStack;
- if ATopOfStack = nil then
- TopOfStack := GetStackTop
- else
- TopOfStack := TJclAddr(ATopOfStack);
- FModuleInfoList := GlobalModulesList.CreateModulesList;
- if AFirstCaller <> nil then
- begin
- Item := TJclStackInfoItem.Create;
- Item.FStackInfo.CallerAddr := TJclAddr(AFirstCaller);
- Add(Item);
- end;
- {$IFDEF CPU32}
- if DelayedTrace then
- DelayStoreStack
- else
- if Raw then
- TraceStackRaw
- else
- TraceStackFrames;
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- CaptureBackTrace;
- {$ENDIF CPU64}
- end;
- destructor TJclStackInfoList.Destroy;
- begin
- if Assigned(FStackData) then
- FreeMem(FStackData);
- GlobalModulesList.FreeModulesList(FModuleInfoList);
- inherited Destroy;
- if (InterlockedDecrement(GlobalStackListLiveCount) = 0) and JclDebugFinalized then
- FreeJclDebugGlobals;
- end;
- {$IFDEF CPU64}
- procedure TJclStackInfoList.CaptureBackTrace;
- const
- InternalSkipFrames = 1; // skip this method
- var
- BackTrace: array [0..127] of Pointer;
- MaxFrames: Integer;
- Hash: DWORD;
- I: Integer;
- StackInfo: TStackInfo;
- CapturedFramesCount: Word;
- begin
- if JclCheckWinVersion(6, 0) then
- MaxFrames := Length(BackTrace)
- else
- begin
- // For XP and 2003 sum of FramesToSkip and FramesToCapture must be lower than 63
- MaxFrames := 62 - InternalSkipFrames;
- end;
- ResetMemory(BackTrace, SizeOf(BackTrace));
- CapturedFramesCount := CaptureStackBackTrace(InternalSkipFrames, MaxFrames, @BackTrace, Hash);
- ResetMemory(StackInfo, SizeOf(StackInfo));
- for I := 0 to CapturedFramesCount - 1 do
- begin
- StackInfo.CallerAddr := TJclAddr(BackTrace[I]);
- StackInfo.Level := I;
- StoreToList(StackInfo); // skips all frames with a level less than "IgnoreLevels"
- end;
- end;
- {$ENDIF CPU64}
- procedure TJclStackInfoList.ForceStackTracing;
- begin
- if DelayedTrace and Assigned(FStackData) and not FInStackTracing then
- begin
- FInStackTracing := True;
- try
- if Raw then
- TraceStackRaw
- else
- TraceStackFrames;
- if FCorrectOnAccess then
- CorrectExceptStackListTop(Self, FSkipFirstItem);
- finally
- FInStackTracing := False;
- FDelayedTrace := False;
- end;
- end;
- end;
- function TJclStackInfoList.GetCount: Integer;
- begin
- ForceStackTracing;
- Result := inherited Count;
- end;
- procedure TJclStackInfoList.CorrectOnAccess(ASkipFirstItem: Boolean);
- begin
- FCorrectOnAccess := True;
- FSkipFirstItem := ASkipFirstItem;
- end;
- procedure TJclStackInfoList.AddToStrings(Strings: TStrings; IncludeModuleName, IncludeAddressOffset,
- IncludeStartProcLineOffset, IncludeVAddress: Boolean);
- var
- I: Integer;
- S: string;
- begin
- ForceStackTracing;
- Strings.BeginUpdate;
- try
- BeginGetLocationInfoCache;
- try
- for I := 0 to Count - 1 do
- begin
- S := GetLocationInfoStr(Items[I].CallerAddr, IncludeModuleName, IncludeAddressOffset,
- IncludeStartProcLineOffset, IncludeVAddress);
- Strings.Add(S);
- end;
- finally
- EndGetLocationInfoCache;
- end;
- finally
- Strings.EndUpdate;
- end;
- end;
- function TJclStackInfoList.GetItems(Index: TJclListSize): TJclStackInfoItem;
- begin
- ForceStackTracing;
- Result := TJclStackInfoItem(Get(Index));
- end;
- function TJclStackInfoList.NextStackFrame(var StackFrame: PStackFrame; var StackInfo: TStackInfo): Boolean;
- var
- CallInstructionSize: Cardinal;
- StackFrameCallerFrame, NewFrame: TJclAddr;
- StackFrameCallerAddr: TJclAddr;
- begin
- // Only report this stack frame into the StockInfo structure
- // if the StackFrame pointer, the frame pointer and the return address on the stack
- // are valid addresses
- StackFrameCallerFrame := StackInfo.CallerFrame;
- while ValidStackAddr(TJclAddr(StackFrame)) do
- begin
- // CallersEBP above the previous CallersEBP
- NewFrame := StackFrame^.CallerFrame;
- if NewFrame <= StackFrameCallerFrame then
- Break;
- StackFrameCallerFrame := NewFrame;
- // CallerAddr within current process space, code segment etc.
- // CallerFrame within current thread stack. Added Mar 12 2002 per Hallvard's suggestion
- StackFrameCallerAddr := StackFrame^.CallerAddr;
- if ValidCodeAddr(StackFrameCallerAddr, FModuleInfoList) and ValidStackAddr(StackFrameCallerFrame + FStackOffset) then
- begin
- Inc(StackInfo.Level);
- StackInfo.StackFrame := StackFrame;
- StackInfo.ParamPtr := PDWORD_PTRArray(TJclAddr(StackFrame) + SizeOf(TStackFrame));
- if StackFrameCallerFrame > StackInfo.CallerFrame then
- StackInfo.CallerFrame := StackFrameCallerFrame
- else
- // the frame pointer points to an address that is below
- // the last frame pointer, so it must be invalid
- Break;
- // Calculate the address of caller by subtracting the CALL instruction size (if possible)
- if ValidCallSite(StackFrameCallerAddr, CallInstructionSize) then
- StackInfo.CallerAddr := StackFrameCallerAddr - CallInstructionSize
- else
- StackInfo.CallerAddr := StackFrameCallerAddr;
- // the stack may be messed up in big projects, avoid overflow in arithmetics
- if StackFrameCallerFrame + FStackOffset < TJclAddr(StackFrame) then
- Break;
- StackInfo.DumpSize := StackFrameCallerFrame + FStackOffset - TJclAddr(StackFrame);
- StackInfo.ParamSize := (StackInfo.DumpSize - SizeOf(TStackFrame)) div 4;
- if PStackFrame(StackFrame^.CallerFrame + FStackOffset) = StackFrame then
- Break;
- // Step to the next stack frame by following the frame pointer
- StackFrame := PStackFrame(StackFrameCallerFrame + FStackOffset);
- Result := True;
- Exit;
- end;
- // Step to the next stack frame by following the frame pointer
- StackFrame := PStackFrame(StackFrameCallerFrame + FStackOffset);
- end;
- Result := False;
- end;
- procedure TJclStackInfoList.StoreToList(const StackInfo: TStackInfo);
- var
- Item: TJclStackInfoItem;
- begin
- if ((IgnoreLevels = -1) and (StackInfo.Level > 0)) or
- (StackInfo.Level > (IgnoreLevels + 1)) then
- begin
- Item := TJclStackInfoItem.Create;
- Item.FStackInfo := StackInfo;
- Add(Item);
- end;
- end;
- procedure TJclStackInfoList.TraceStackFrames;
- var
- StackFrame: PStackFrame;
- StackInfo: TStackInfo;
- begin
- Capacity := 32; // reduce ReallocMem calls, must be > 1 because the caller's EIP register is already in the list
- // Start at level 0
- StackInfo.Level := 0;
- StackInfo.CallerFrame := 0;
- if DelayedTrace then
- // Get the current stack frame from the frame register
- StackFrame := FFramePointer
- else
- begin
- // We define the bottom of the valid stack to be the current ESP pointer
- if BaseOfStack = 0 then
- BaseOfStack := TJclAddr(GetFramePointer);
- // Get a pointer to the current bottom of the stack
- StackFrame := PStackFrame(BaseOfStack);
- end;
- // We define the bottom of the valid stack to be the current frame Pointer
- // There is a TIB field called pvStackUserBase, but this includes more of the
- // stack than what would define valid stack frames.
- BaseOfStack := TJclAddr(StackFrame) - 1;
- // Loop over and report all valid stackframes
- while NextStackFrame(StackFrame, StackInfo) and (inherited Count <> MaxStackTraceItems) do
- StoreToList(StackInfo);
- end;
- function TraceStackInstuctions(Proc, InstructionAddr: Pointer; ModuleEndAddr: TJclAddr;
- var LocalVarStackOffset, ParamStackOffset: Integer): Boolean;
- const
- PointerSize = SizeOf(Pointer);
- function ParseSaveRegisters(ProcAddr, CallAddr: TJclAddr; var RegisterStackOffset: Integer): TJclAddr;
- var
- P: PByteArray;
- begin
- Result := ProcAddr;
- while Result < TJclAddr(CallAddr) do
- begin
- P := PByteArray(Result);
- if (P[0] and $F8) = $50 then // PUSH r32
- begin
- Inc(RegisterStackOffset, PointerSize);
- Inc(Result);
- Continue;
- end;
- Break;
- end;
- end;
- function CheckRegisterRestoreBackwards(ProcAddr, CallAddr: TJclAddr; var RegisterStackOffset: Integer): Boolean;
- var
- Count: Integer;
- begin
- if RegisterStackOffset > 0 then
- begin
- Count := 0;
- while (ProcAddr > CallAddr) and (PByte(ProcAddr)^ and $F8 = $58) do // POP r32
- begin
- Dec(ProcAddr);
- Inc(Count);
- end;
- if (Count > 0) and (Cardinal(Count) <= Cardinal(RegisterStackOffset) div PointerSize) then
- begin
- // We may have used a "function call push" in the prolog analysis so fix this
- RegisterStackOffset := Count * PointerSize;
- Result := True;
- end
- else
- Result := False;
- end
- else
- Result := True;
- end;
- function ParseEspChange(ProcAddr, CallAddr: TJclAddr; var LocalVarStackOffset: Integer; var EspChangeFound: Boolean): TJclAddr;
- var
- P: PByteArray;
- begin
- Result := ProcAddr;
- P := PByteArray(Result);
- if (Result + 3 < TJclAddr(CallAddr)) and (P[0] = $83) and (P[1] = $C4) then // 83C4F8 add esp,imm8
- begin
- Inc(LocalVarStackOffset, -Integer(ShortInt(P[2])));
- EspChangeFound := True;
- Inc(Result, 3);
- end
- else if (Result + 6 < TJclAddr(CallAddr)) and (P[0] = $81) and (P[1] = $C4) then // 81C408000100 add esp,imm32
- begin
- Inc(LocalVarStackOffset, -PInteger(@P[2])^);
- EspChangeFound := True;
- Inc(Result, 6);
- end;
- end;
- function CheckEspChangeBackwards(ProcAddr, CallAddr: TJclAddr): Boolean;
- var
- Offset: Integer;
- begin
- Inc(ProcAddr);
- Result := False;
- if ProcAddr - 3 >= CallAddr then
- begin
- ParseEspChange(ProcAddr - 3, ProcAddr + 1, Offset, Result);
- if Result then
- Exit;
- end;
- if ProcAddr - 6 >= CallAddr then
- begin
- ParseEspChange(ProcAddr - 6, ProcAddr + 1, Offset, Result);
- if Result then
- Exit;
- end;
- end;
- function CheckStackAddressValidation(ProcAddr, CallAddr: TJclAddr; var LocalVarStackOffset: Integer;
- var EspChangeFound: Boolean): Integer;
- var
- P: PByteArray;
- begin
- // The compiler emits multiple functino prologues to probe the stack frame memory pages.
- P := PByteArray(ProcAddr);
- if (ProcAddr + 6 < CallAddr) and
- (P[0] = $81) and (P[1] = $C4) and (PInteger(@P[2])^ = -4092) and // 81C404F0FFFF add esp,$fffff004
- (P[6] = $50) then // 50 push eax
- begin
- Inc(LocalVarStackOffset, (4092+4));
- EspChangeFound := True;
- Result := 7;
- end
- else if (ProcAddr + 8 < CallAddr) and // CompilerSpeedPack option -x-fpr
- (P[0] = $81) and (P[1] = $C4) and (PInteger(@P[2])^ = -4096) and // 81C404F0FFFF add esp,$fffff000
- (P[6] = $85) and (P[7] = $24) and (P[8] = $24) then // 852424 test [esp],esp
- begin
- Inc(LocalVarStackOffset, 4096);
- EspChangeFound := True;
- Result := 9;
- end
- else if (ProcAddr + 17 + 4 < CallAddr) and
- (P[0] = $50) and // 50 push eax
- (P[1] = $B8) and // B804000000 mov eax,imm32
- (P[6] = $81) and (P[7] = $C4) and (PInteger(@P[8])^ = -4092) and // 81C404F0FFFF add esp,$fffff004
- (P[12] = $50) and // 50 push eax
- (P[13] = $48) and // 48 dec eax
- (P[14] = $75) and (P[15] = $F6) and // 75F6 jnz -10
- (P[16] = $8B) and ((PWord(@P[16])^ = $2484) or (P[17] = $45)) then // 8B842400000100 mov eax,[esp+imm32] / 8B45FC mov eax,[ebp-imm8]
- begin
- Inc(LocalVarStackOffset, PInteger(@P[2])^ * (4092+4));
- EspChangeFound := True;
- Result := 19;
- if P[17] = $45 then
- Inc(Result, 1) // 8B45FC mov eax,[ebp-imm8]
- else
- Inc(Result, 4); // 8B842400000100 mov eax,[esp+imm32]
- end
- else if (ProcAddr + 20 + 4 < CallAddr) and // CompilerSpeedPack option -x-fpr
- (P[0] = $50) and // 50 push eax
- (P[1] = $B8) and // B804000000 mov eax,imm32
- (P[6] = $81) and (P[7] = $C4) and (PInteger(@P[8])^ = -4096) and // 81C404F0FFFF add esp,$fffff000
- (P[12] = $85) and (P[13] = $24) and (P[14] = $24) and // 852424 test [esp],esp
- (P[15] = $48) and // 48 dec eax
- (P[16] = $75) and (P[17] = $F6) and // 75F6 jnz -10
- (P[18] = $8B) and ((PWord(@P[19])^ = $2484) or (P[19] = $45)) then // 8B842400000100 mov eax,[esp+imm32] / 8B45FC mov eax,[ebp-imm8]
- begin
- Inc(LocalVarStackOffset, PInteger(@P[2])^ * 4096);
- EspChangeFound := True;
- Result := 21;
- if P[19] = $45 then
- Inc(Result, 1) // 8B45FC mov eax,[ebp-imm8]
- else
- Inc(Result, 4); // 8B842400000100 mov eax,[esp+imm32]
- end
- else if (ProcAddr + 2 < CallAddr) and
- (P[0] = $33) and (P[1] = $C9) and // 33C9 xor ecx,ecx
- (P[2] = $51) then // 51 push ecx
- begin
- Inc(LocalVarStackOffset, 4);
- EspChangeFound := True;
- Result := 1;
- Inc(ProcAddr, 3);
- while (ProcAddr + 2 < CallAddr) and (PByte(ProcAddr)^ = $51) do
- begin
- Inc(ProcAddr);
- Inc(Result);
- end;
- Inc(LocalVarStackOffset, 4 * Result);
- Inc(Result, 2); // xor ecx, ecx
- end
- // Compiler sets the stack for managed local variables to zero
- else if (ProcAddr + 12 < CallAddr) and
- (P[0] = $51) and // 51 push ecx
- (P[1] = $B9) and // imm32 // B906000000 mov ecx,imm32
- (P[6] = $6A) and (P[7] = $00) and // 6A00 push $00
- (P[8] = $6A) and (P[9] = $00) and // 6A00 push $00
- (P[10] = $49) and // 49 dec ecx
- (P[11] = $75) and (P[12] = $F9) then // 75F9 jnz -7
- begin
- Inc(LocalVarStackOffset, PInteger(@P[2])^ * PointerSize * 2);
- EspChangeFound := True;
- Result := 13;
- // For an odd number of local DWORDs the compiler emits an additional "push ecx"
- if (ProcAddr + 13 < CallAddr) and
- (P[13] = $51) then // 51 push ecx
- begin
- Inc(LocalVarStackOffset, PointerSize);
- Inc(Result, 1);
- end;
- if (ProcAddr + TJclAddr(Result) + 3 < CallAddr) and
- (P[Result + 0] = $87) and (P[Result + 1] = $4D) then // imm8 // 874DFC xchg [ebp-imm8],ecx
- begin
- Inc(Result, 3);
- end
- else if (ProcAddr + TJclAddr(Result) + 10 < CallAddr) and // CompilerSpeedPack option -x-fpr
- (P[Result + 0] = $8B) and (P[Result + 1] = $4D) and //imm8 // 8B4DFC mov ecx,[ebp-imm8]
- (P[Result + 3] = $C7) and (P[Result + 4] = $45) and (P[Result + 5] = $FC) and // C745FC00000000 mov [ebp-$04],$00000000
- (PInteger(@P[Result + 6])^ = 0) then
- begin
- Inc(Result, 10);
- end;
- end
- else
- Result := 0;
- end;
- var
- P: PByteArray;
- ProcAddr, CallAddr, EpilogAddr: TJclAddr;
- StackFrameFound: Integer;
- RegisterStackOffset: Integer;
- EspChangeFound: Boolean;
- Size: Integer;
- PossibleEndFound: Boolean;
- EpilogInfo: TJclLocationInfo;
- RegStackOffset: Integer;
- begin
- LocalVarStackOffset := 0;
- ParamStackOffset := 0;
- RegisterStackOffset := 0;
- Result := False;
- if Proc = nil then
- Exit;
- ProcAddr := TJclAddr(Proc);
- CallAddr := TJclAddr(InstructionAddr);
- // Prolog: stackframe
- StackFrameFound := 0;
- EspChangeFound := False;
- if ProcAddr < CallAddr then
- begin
- P := PByteArray(ProcAddr);
- if (P[0] = $55) and // PUSH EBP
- (P[1] = $8B) and (P[2] = $EC) then // MOV EBP,ESP
- begin
- LocalVarStackOffset := PointerSize; // EBP
- StackFrameFound := 1; // Epilog must end with "POP EBP"
- Inc(ProcAddr, 3);
- end
- else if (P[0] = $C8) and (ProcAddr + 4 < CallAddr) then // ENTER Size(Word), NestingLevel(Byte)
- begin
- LocalVarStackOffset := PointerSize + PWord(@P[1])^ + PointerSize*P[3]; // EBP + Size + 4*NestingLevel
- StackFrameFound := -1; // Epilog must end with "LEAVE"
- Inc(ProcAddr, 4);
- end;
- end;
- if StackFrameFound = 0 then
- begin
- // Prolog: save registers
- // If we have no stackframe, then the compiler saves the registers before allocating stack variables.
- // RegisterStackOffset is preliminary because it may be reset by Epilog's POP code that is more
- // accurate because we can't distinguish between the save register and an immediatelly following
- // function parameter "PUSH".
- ProcAddr := ParseSaveRegisters(ProcAddr, CallAddr, {var} RegisterStackOffset);
- // Prolog: no stackframe + stack address validation
- Size := 0;
- if RegisterStackOffset >= PointerSize then
- begin
- // If there is a "push eax", then the ParseSaveRegisters handled it, but it may be the
- // stack validation's "push eax".
- Size := CheckStackAddressValidation(ProcAddr - 1{push eax}, CallAddr, {var} LocalVarStackOffset, {var} EspChangeFound);
- if Size > 0 then
- begin
- Dec(RegisterStackOffset, PointerSize);
- Dec(ProcAddr);
- end;
- end;
- if Size = 0 then
- Size := CheckStackAddressValidation(ProcAddr, CallAddr, {var} LocalVarStackOffset, {var} EspChangeFound);
- Inc(ProcAddr, Size);
- ProcAddr := ParseEspChange(ProcAddr, CallAddr, LocalVarStackOffset, {var} EspChangeFound);
- end
- else
- begin
- // Prolog: stackframe + stack address validation
- Size := CheckStackAddressValidation(ProcAddr, CallAddr, {var} LocalVarStackOffset, {var} EspChangeFound);
- Inc(ProcAddr, Size);
- ProcAddr := ParseEspChange(ProcAddr, CallAddr, LocalVarStackOffset, {var} EspChangeFound);
- // If we have a stackframe, then the compiler saves the registers after allocating stack variables.
- ProcAddr := ParseSaveRegisters(ProcAddr, CallAddr, {var} RegisterStackOffset);
- end;
- // Find not closed try/finally/except blocks and add them the LocalVarStackOffset
- while (ProcAddr < CallAddr) and (ProcAddr < ModuleEndAddr) do
- begin
- // fast forward find for XOR EAX,EAX
- while (ProcAddr < CallAddr) and (ProcAddr < ModuleEndAddr) and (PByteArray(ProcAddr)[0] <> $33) do
- Inc(ProcAddr);
- P := PByteArray(ProcAddr);
- // Find all occurrences above the CallAddr and add to LocalVarStackOffset (3*PointerSize)
- // "try"
- // 33C0 xor eax,eax
- // 55 push ebp
- // 68E9E05000 push $0050e0e9
- // 64FF30 push dword ptr fs:[eax]
- // 648920 mov fs:[eax],esp
- if (ProcAddr + 13 < CallAddr) and
- (P[0] = $33) and (P[1] = $C0) and
- (P[2] = $55) and
- (P[3] = $68) and
- (P[8] = $64) and (P[9] = $FF) and (P[10] = $30) and
- (P[11] = $64) and (P[12] = $89) and (P[13] = $20) then
- begin
- Inc(LocalVarStackOffset, 3 * PointerSize);
- end
- // "finally"/"except"
- // Find all occurrences above the CallAddr and substract from LocalVarStackOffset (3*PointerSize)
- // 33C0 xor eax,eax
- // 5A pop edx
- // 59 pop ecx
- // 59 pop ecx
- // 648910 mov fs:[eax],edx
- else if (ProcAddr + 7 < CallAddr) and
- (P[0] = $33) and (P[1] = $C0) and
- (P[2] = $5A) and
- (P[3] = $59) and
- (P[4] = $59) and
- (P[5] = $64) and (P[6] = $89) and (P[7] = $10) then
- begin
- Dec(LocalVarStackOffset, 3 * PointerSize);
- end;
- Inc(ProcAddr);
- end;
- // Find the epilog to obtain the ParamStackOffset (would be much easier and less guess work
- // if we knew the exact function's end address)
- ProcAddr := CallAddr;
- while ProcAddr < ModuleEndAddr do
- begin
- // fast forward find for RET / RET imm16
- while (ProcAddr < ModuleEndAddr) and not (PByteArray(ProcAddr)[0] in [$C3, $C2]) do
- Inc(ProcAddr);
- P := PByteArray(ProcAddr);
- // We may have found the RET of a finally clause
- if (ProcAddr + 7 < ModuleEndAddr) and // skip "finally" code
- (P[0] = $C3) and // C3 ret
- (P[1] = $E9) and // E91821FAFF jmp @HandleFinally
- (P[6] = $EB) and (ShortInt(P[7]) < 0) then // EBF8 jmp imm8
- begin
- Inc(ProcAddr, 8);
- end
- else if (ProcAddr + 10 < ModuleEndAddr) and // skip "finally" code
- (P[0] = $C3) and // C3 ret
- (P[1] = $E9) and // E91821FAFF jmp @HandleFinally
- (P[6] = $E9) and (PInteger(@P[7])^ < 0) then // E9xxxxxxxx jmp imm32
- begin
- Inc(ProcAddr, 11);
- end
- else if (P[0] = $C3) or ((P[0] = $C2) and (ProcAddr + 3 < ModuleEndAddr)) then
- begin
- EpilogAddr := ProcAddr;
- PossibleEndFound := False;
- if StackFrameFound = 1 then
- begin
- // If we have a stackframe, then we verify that the stackframe is cleared to check
- // if we found a valid "RET"
- if EspChangeFound then
- EpilogAddr := EpilogAddr - 3
- else
- EpilogAddr := EpilogAddr - 1;
- if EpilogAddr >= CallAddr then
- begin
- P := PByteArray(EpilogAddr);
- if EspChangeFound and
- (P[0] = $8B) and (P[1] = $E5) and // 8BE5 mov esp,ebp
- (P[2] = $5D) then // 5D pop ebp
- begin
- Dec(EpilogAddr);
- PossibleEndFound := True;
- end
- else if not EspChangeFound and
- (P[0] = $5D) then // 5D pop ebp
- begin
- Dec(EpilogAddr);
- PossibleEndFound := True;
- end;
- end;
- end
- else if StackFrameFound = -1 then
- begin
- // If we have a ENTER/LEAVE stackframe, then we verify that the stackframe is cleared
- // to check if we found a valid "RET"
- Dec(EpilogAddr);
- P := PByteArray(EpilogAddr);
- if (EpilogAddr >= CallAddr) and (P[0] = $C9) then // LEAVE
- begin
- Dec(EpilogAddr);
- PossibleEndFound := True;
- end;
- end
- else
- begin
- // If we have no stackframe, then we can't verify the validity of the "RET" here
- EpilogAddr := EpilogAddr - 1;
- PossibleEndFound := True;
- end;
- if PossibleEndFound then
- begin
- if GetLocationInfo(Pointer(EpilogAddr), EpilogInfo) and
- (TJclAddr(EpilogInfo.OffsetFromProcName) <> EpilogAddr - TJclAddr(Proc)) then
- begin
- // If we didn't find a RET in the same procedure then the analysis failed
- Exit;
- end;
- if PossibleEndFound then
- begin
- // If we have registers saved on the stack, we can use those to verify if the
- // found "RET" is valid.
- RegStackOffset := RegisterStackOffset;
- if CheckRegisterRestoreBackwards(EpilogAddr, CallAddr, {var} RegStackOffset) then
- begin
- if (StackFrameFound = 0) and EspChangeFound then
- begin
- // If we have local variables (ESP was changed in the prolog) we can use that
- // information to verify the "RET"
- EpilogAddr := EpilogAddr - TJclAddr(RegStackOffset) div PointerSize;
- if not CheckEspChangeBackwards(EpilogAddr, CallAddr) then
- PossibleEndFound := False;
- end;
- if PossibleEndFound then
- begin
- RegisterStackOffset := RegStackOffset;
- if PByte(ProcAddr)^ = $C2 then
- ParamStackOffset := PWord(ProcAddr + 1)^
- else
- begin
- // TODO: if we only have a "RET" at the end we need to look at the call instruction
- // if it is followed by a "sub/add esp,xx" for a "cdecl" function. (What if the add/sub
- // is for the caller's epilog?)
- end;
- Break;
- end;
- end;
- end;
- end;
- end;
- Inc(ProcAddr);
- end;
- Inc(LocalVarStackOffset, RegisterStackOffset);
- Result := True;
- end;
- procedure TJclStackInfoList.TraceStackRaw;
- var
- StackInfo: TStackInfo;
- StackPtr: PJclAddr;
- PrevCaller: TJclAddr;
- CallInstructionSize: Cardinal;
- StackTop: TJclAddr;
- ProcInfo: TJclLocationInfo;
- ProcStart: Pointer;
- CallInstructionPtr: Pointer;
- LocalVarStackOffset, ParamStackOffset: Integer;
- ModuleEndAddr: TJclAddr;
- begin
- Capacity := 32; // reduce ReallocMem calls, must be > 1 because the caller's EIP register is already in the list
- if DelayedTrace then
- begin
- if not Assigned(FStackData) then
- Exit;
- StackPtr := PJclAddr(FStackData);
- end
- else
- begin
- // We define the bottom of the valid stack to be the current ESP pointer
- if BaseOfStack = 0 then
- BaseOfStack := TJclAddr(GetStackPointer);
- // Get a pointer to the current bottom of the stack
- StackPtr := PJclAddr(BaseOfStack);
- end;
- StackTop := TopOfStack;
- // We will not be able to fill in all the fields in the StackInfo record,
- // so just blank it all out first
- ResetMemory(StackInfo, SizeOf(StackInfo));
- // Clear the previous call address
- PrevCaller := 0;
- // stCleanRawStack: We don't know the number of parameters for the "initial" function
- ParamStackOffset := 0;
- if stCleanRawStack in JclStackTrackingOptions then
- BeginGetLocationInfoCache; // speed up the GetLocationInfo calls
- // Loop through all of the valid stack space
- try
- while (TJclAddr(StackPtr) < StackTop) and (inherited Count <> MaxStackTraceItems) do
- begin
- // If the current DWORD on the stack refers to a valid call site...
- if ValidCallSite(StackPtr^, CallInstructionSize) and (StackPtr^ <> PrevCaller) then
- begin
- // then pick up the callers address
- StackInfo.CallerAddr := StackPtr^ - CallInstructionSize;
- // remember to callers address so that we don't report it repeatedly
- PrevCaller := StackPtr^;
- // increase the stack level
- Inc(StackInfo.Level);
- // then report it back to our caller
- StoreToList(StackInfo);
- if stCleanRawStack in JclStackTrackingOptions then
- begin
- // Skip all stack parameters of the last called function
- Inc(PByte(StackPtr), ParamStackOffset);
- ParamStackOffset := 0;
- CallInstructionPtr := Pointer(StackInfo.CallerAddr);
- if GetLocationInfo(CallInstructionPtr, ProcInfo) then
- begin
- if ProcInfo.ProcedureName <> '' then
- begin
- if (ProcInfo.ProcedureName[1] = '@') and (ProcInfo.ProcedureName = '@RaiseExcept$qqrv') then
- begin
- // Special handling for _RaiseExcept because it does a lot to the stack including
- // putting the ExceptAddr multiple times on the stack causing TraceStackInstuctions to
- // change the StackPtr to the wrong locations.
- LocalVarStackOffset := 17 * SizeOf(Pointer);
- ParamStackOffset := 6 * SizeOf(Pointer);
- Inc(PByte(StackPtr), LocalVarStackOffset);
- end
- else
- begin
- ProcStart := Pointer(TJclAddr(CallInstructionPtr) - TJclAddr(ProcInfo.OffsetFromProcName));
- ModuleEndAddr := TJclAddr(ProcInfo.DebugInfo.Module) + ModuleCodeOffset + TJclAddr(ProcInfo.DebugInfo.ModuleCodeSize);
- if TraceStackInstuctions(ProcStart, CallInstructionPtr, ModuleEndAddr, LocalVarStackOffset, ParamStackOffset) then
- Inc(PByte(StackPtr), LocalVarStackOffset) // skip all local variables (and saved registers)
- else
- ParamStackOffset := 0; // Don't skip stack entries if TraceStackInstuctions failed
- end;
- end;
- end;
- end;
- end;
- // Look at the next DWORD on the stack
- Inc(StackPtr);
- end;
- finally
- if stCleanRawStack in JclStackTrackingOptions then
- EndGetLocationInfoCache;
- if Assigned(FStackData) then
- begin
- FreeMem(FStackData);
- FStackData := nil;
- end;
- end;
- end;
- {$IFDEF CPU32}
- procedure TJclStackInfoList.DelayStoreStack;
- var
- StackPtr: PJclAddr;
- StackDataSize: Cardinal;
- begin
- if Assigned(FStackData) then
- begin
- FreeMem(FStackData);
- FStackData := nil;
- end;
- // We define the bottom of the valid stack to be the current ESP pointer
- if BaseOfStack = 0 then
- begin
- BaseOfStack := TJclAddr(GetStackPointer);
- FFramePointer := GetFramePointer;
- end;
- // Get a pointer to the current bottom of the stack
- StackPtr := PJclAddr(BaseOfStack);
- if TJclAddr(StackPtr) < TopOfStack then
- begin
- StackDataSize := TopOfStack - TJclAddr(StackPtr);
- GetMem(FStackData, StackDataSize);
- System.Move(StackPtr^, FStackData^, StackDataSize);
- end;
- FStackOffset := Int64(FStackData) - Int64(StackPtr);
- FFramePointer := Pointer(TJclAddr(FFramePointer) + FStackOffset);
- TopOfStack := TopOfStack + FStackOffset;
- end;
- {$ENDIF CPU32}
- // Validate that the code address is a valid code site
- //
- // Information from Intel Manual 24319102(2).pdf, Download the 6.5 MBs from:
- // http://developer.intel.com/design/pentiumii/manuals/243191.htm
- // Instruction format, Chapter 2 and The CALL instruction: page 3-53, 3-54
- function TJclStackInfoList.ValidCallSite(CodeAddr: TJclAddr; out CallInstructionSize: Cardinal): Boolean;
- var
- CodeDWORD4: DWORD;
- CodeDWORD8: DWORD;
- C4P, C8P: PDWORD;
- RM1, RM2, RM5: Byte;
- begin
- // todo: 64 bit version
- // First check that the address is within range of our code segment!
- Result := CodeAddr > 8;
- if Result then
- begin
- C8P := PDWORD(CodeAddr - 8);
- C4P := PDWORD(CodeAddr - 4);
- Result := ValidCodeAddr(TJclAddr(C8P), FModuleInfoList) and not IsBadReadPtr(C8P, 8);
- // Now check to see if the instruction preceding the return address
- // could be a valid CALL instruction
- if Result then
- begin
- try
- CodeDWORD8 := PDWORD(C8P)^;
- CodeDWORD4 := PDWORD(C4P)^;
- // CodeDWORD8 = (ReturnAddr-5):(ReturnAddr-6):(ReturnAddr-7):(ReturnAddr-8)
- // CodeDWORD4 = (ReturnAddr-1):(ReturnAddr-2):(ReturnAddr-3):(ReturnAddr-4)
- // ModR/M bytes contain the following bits:
- // Mod = (76)
- // Reg/Opcode = (543)
- // R/M = (210)
- RM1 := (CodeDWORD4 shr 24) and $7;
- RM2 := (CodeDWORD4 shr 16) and $7;
- //RM3 := (CodeDWORD4 shr 8) and $7;
- //RM4 := CodeDWORD4 and $7;
- RM5 := (CodeDWORD8 shr 24) and $7;
- //RM6 := (CodeDWORD8 shr 16) and $7;
- //RM7 := (CodeDWORD8 shr 8) and $7;
- // Check the instruction prior to the potential call site.
- // We consider it a valid call site if we find a CALL instruction there
- // Check the most common CALL variants first
- if ((CodeDWORD8 and $FF000000) = $E8000000) then
- // 5 bytes, "CALL NEAR REL32" (E8 cd)
- CallInstructionSize := 5
- else
- if ((CodeDWORD4 and $F8FF0000) = $10FF0000) and not (RM1 in [4, 5]) then
- // 2 bytes, "CALL NEAR [EAX]" (FF /2) where Reg = 010, Mod = 00, R/M <> 100 (1 extra byte)
- // and R/M <> 101 (4 extra bytes)
- CallInstructionSize := 2
- else
- if ((CodeDWORD4 and $F8FF0000) = $D0FF0000) then
- // 2 bytes, "CALL NEAR EAX" (FF /2) where Reg = 010 and Mod = 11
- CallInstructionSize := 2
- else
- if ((CodeDWORD4 and $00FFFF00) = $0014FF00) then
- // 3 bytes, "CALL NEAR [EAX+EAX*i]" (FF /2) where Reg = 010, Mod = 00 and RM = 100
- // SIB byte not validated
- CallInstructionSize := 3
- else
- if ((CodeDWORD4 and $00F8FF00) = $0050FF00) and (RM2 <> 4) then
- // 3 bytes, "CALL NEAR [EAX+$12]" (FF /2) where Reg = 010, Mod = 01 and RM <> 100 (1 extra byte)
- CallInstructionSize := 3
- else
- if ((CodeDWORD4 and $0000FFFF) = $000054FF) then
- // 4 bytes, "CALL NEAR [EAX+EAX+$12]" (FF /2) where Reg = 010, Mod = 01 and RM = 100
- // SIB byte not validated
- CallInstructionSize := 4
- else
- if ((CodeDWORD8 and $FFFF0000) = $15FF0000) then
- // 6 bytes, "CALL NEAR [$12345678]" (FF /2) where Reg = 010, Mod = 00 and RM = 101
- CallInstructionSize := 6
- else
- if ((CodeDWORD8 and $F8FF0000) = $90FF0000) and (RM5 <> 4) then
- // 6 bytes, "CALL NEAR [EAX+$12345678]" (FF /2) where Reg = 010, Mod = 10 and RM <> 100 (1 extra byte)
- CallInstructionSize := 6
- else
- if ((CodeDWORD8 and $00FFFF00) = $0094FF00) then
- // 7 bytes, "CALL NEAR [EAX+EAX+$1234567]" (FF /2) where Reg = 010, Mod = 10 and RM = 100
- CallInstructionSize := 7
- else
- if ((CodeDWORD8 and $0000FF00) = $00009A00) then
- // 7 bytes, "CALL FAR $1234:12345678" (9A ptr16:32)
- CallInstructionSize := 7
- else
- Result := False;
- // Because we're not doing a complete disassembly, we will potentially report
- // false positives. If there is odd code that uses the CALL 16:32 format, we
- // can also get false negatives.
- except
- Result := False;
- end;
- end;
- end;
- end;
- {$IFNDEF STACKFRAMES_ON}
- {$STACKFRAMES OFF}
- {$ENDIF ~STACKFRAMES_ON}
- function TJclStackInfoList.ValidStackAddr(StackAddr: TJclAddr): Boolean;
- begin
- Result := (BaseOfStack < StackAddr) and (StackAddr < TopOfStack);
- end;
- //=== Exception frame info routines ==========================================
- function JclCreateExceptFrameList(AIgnoreLevels: Integer): TJclExceptFrameList;
- begin
- Result := TJclExceptFrameList.Create(AIgnoreLevels);
- GlobalStackList.AddObject(Result);
- end;
- function JclLastExceptFrameList: TJclExceptFrameList;
- begin
- Result := GlobalStackList.LastExceptFrameList[GetCurrentThreadID];
- end;
- function JclGetExceptFrameList(ThreadID: DWORD): TJclExceptFrameList;
- begin
- Result := GlobalStackList.LastExceptFrameList[ThreadID];
- end;
- procedure DoExceptFrameTrace;
- begin
- // Ignore first 2 levels; the First level is an undefined frame (I haven't a
- // clue as to where it comes from. The second level is the try..finally block
- // in DoExceptNotify.
- JclCreateExceptFrameList(4);
- end;
- {$OVERFLOWCHECKS OFF}
- function GetJmpDest(Jmp: PJmpInstruction): Pointer;
- begin
- // TODO : 64 bit version
- if Jmp^.opCode = $E9 then
- Result := Pointer(TJclAddr(Jmp) + TJclAddr(Jmp^.distance) + 5)
- else
- if Jmp.opCode = $EB then
- Result := Pointer(TJclAddr(Jmp) + TJclAddr(ShortInt(Jmp^.distance)) + 2)
- else
- Result := nil;
- if (Result <> nil) and (PJmpTable(Result).OPCode = $25FF) then
- if not IsBadReadPtr(PJmpTable(Result).Ptr, SizeOf(Pointer)) then
- Result := Pointer(PJclAddr(PJmpTable(Result).Ptr)^);
- end;
- {$IFDEF OVERFLOWCHECKS_ON}
- {$OVERFLOWCHECKS ON}
- {$ENDIF OVERFLOWCHECKS_ON}
- //=== { TJclExceptFrame } ====================================================
- constructor TJclExceptFrame.Create(AFrameLocation: Pointer; AExcDesc: PExcDesc);
- begin
- inherited Create;
- FFrameKind := efkUnknown;
- FFrameLocation := AFrameLocation;
- FCodeLocation := nil;
- AnalyseExceptFrame(AExcDesc);
- end;
- {$RANGECHECKS OFF}
- procedure TJclExceptFrame.AnalyseExceptFrame(AExcDesc: PExcDesc);
- var
- Dest: Pointer;
- LocInfo: TJclLocationInfo;
- FixedProcedureName: string;
- DotPos, I: Integer;
- begin
- Dest := GetJmpDest(@AExcDesc^.Jmp);
- if Dest <> nil then
- begin
- // get frame kind
- LocInfo := GetLocationInfo(Dest);
- if CompareText(LocInfo.UnitName, 'system') = 0 then
- begin
- FixedProcedureName := LocInfo.ProcedureName;
- DotPos := Pos('.', FixedProcedureName);
- if DotPos > 0 then
- FixedProcedureName := Copy(FixedProcedureName, DotPos + 1, Length(FixedProcedureName) - DotPos);
- if CompareText(FixedProcedureName, '@HandleAnyException') = 0 then
- FFrameKind := efkAnyException
- else
- if CompareText(FixedProcedureName, '@HandleOnException') = 0 then
- FFrameKind := efkOnException
- else
- if CompareText(FixedProcedureName, '@HandleAutoException') = 0 then
- FFrameKind := efkAutoException
- else
- if CompareText(FixedProcedureName, '@HandleFinally') = 0 then
- FFrameKind := efkFinally;
- end;
- // get location
- if FFrameKind <> efkUnknown then
- begin
- FCodeLocation := GetJmpDest(PJmpInstruction(TJclAddr(@AExcDesc^.Instructions)));
- if FCodeLocation = nil then
- FCodeLocation := @AExcDesc^.Instructions;
- end
- else
- begin
- FCodeLocation := GetJmpDest(PJmpInstruction(TJclAddr(AExcDesc)));
- if FCodeLocation = nil then
- FCodeLocation := AExcDesc;
- end;
- // get on handlers
- if FFrameKind = efkOnException then
- begin
- SetLength(FExcTab, AExcDesc^.Cnt);
- for I := 0 to AExcDesc^.Cnt - 1 do
- begin
- if AExcDesc^.ExcTab[I].VTable = nil then
- begin
- SetLength(FExcTab, I);
- Break;
- end
- else
- FExcTab[I] := AExcDesc^.ExcTab[I];
- end;
- end;
- end;
- end;
- {$IFDEF RANGECHECKS_ON}
- {$RANGECHECKS ON}
- {$ENDIF RANGECHECKS_ON}
- function TJclExceptFrame.Handles(ExceptObj: TObject): Boolean;
- var
- Handler: Pointer;
- begin
- Result := HandlerInfo(ExceptObj, Handler);
- end;
- {$OVERFLOWCHECKS OFF}
- function TJclExceptFrame.HandlerInfo(ExceptObj: TObject; out HandlerAt: Pointer): Boolean;
- var
- I: Integer;
- ObjVTable, VTable, ParentVTable: Pointer;
- begin
- Result := FrameKind in [efkAnyException, efkAutoException];
- if not Result and (FrameKind = efkOnException) then
- begin
- HandlerAt := nil;
- ObjVTable := Pointer(ExceptObj.ClassType);
- for I := Low(FExcTab) to High(FExcTab) do
- begin
- VTable := ObjVTable;
- Result := FExcTab[I].VTable = nil;
- while (not Result) and (VTable <> nil) do
- begin
- Result := (FExcTab[I].VTable = VTable) or
- (PShortString(PPointer(PJclAddr(FExcTab[I].VTable)^ + TJclAddr(vmtClassName))^)^ =
- PShortString(PPointer(TJclAddr(VTable) + TJclAddr(vmtClassName))^)^);
- if Result then
- HandlerAt := FExcTab[I].Handler
- else
- begin
- ParentVTable := TClass(VTable).ClassParent;
- if ParentVTable = VTable then
- VTable := nil
- else
- VTable := ParentVTable;
- end;
- end;
- if Result then
- Break;
- end;
- end
- else
- if Result then
- HandlerAt := FCodeLocation
- else
- HandlerAt := nil;
- end;
- {$IFDEF OVERFLOWCHECKS_ON}
- {$OVERFLOWCHECKS ON}
- {$ENDIF OVERFLOWCHECKS_ON}
- //=== { TJclExceptFrameList } ================================================
- constructor TJclExceptFrameList.Create(AIgnoreLevels: Integer);
- begin
- inherited Create;
- FIgnoreLevels := AIgnoreLevels;
- TraceExceptionFrames;
- end;
- function TJclExceptFrameList.AddFrame(AFrame: PExcFrame): TJclExceptFrame;
- begin
- Result := TJclExceptFrame.Create(AFrame, AFrame^.Desc);
- Add(Result);
- end;
- function TJclExceptFrameList.GetItems(Index: TJclListSize): TJclExceptFrame;
- begin
- Result := TJclExceptFrame(Get(Index));
- end;
- procedure TJclExceptFrameList.TraceExceptionFrames;
- {$IFDEF CPU32}
- var
- ExceptionPointer: PExcFrame;
- Level: Integer;
- ModulesList: TJclModuleInfoList;
- begin
- Clear;
- ModulesList := GlobalModulesList.CreateModulesList;
- try
- Level := 0;
- ExceptionPointer := GetExceptionPointer;
- while TJclAddr(ExceptionPointer) <> High(TJclAddr) do
- begin
- if (Level >= IgnoreLevels) and ValidCodeAddr(TJclAddr(ExceptionPointer^.Desc), ModulesList) then
- AddFrame(ExceptionPointer);
- Inc(Level);
- ExceptionPointer := ExceptionPointer^.next;
- end;
- finally
- GlobalModulesList.FreeModulesList(ModulesList);
- end;
- end;
- {$ENDIF CPU32}
- {$IFDEF CPU64}
- begin
- // TODO: 64-bit version
- end;
- {$ENDIF CPU64}
- //=== Exception hooking ======================================================
- var
- TrackingActiveCount: Integer;
- IgnoredExceptions: TThreadList = nil;
- IgnoredExceptionClassNames: TStringList = nil;
- IgnoredExceptionClassNamesCritSect: TJclCriticalSection = nil;
- procedure AddIgnoredException(const ExceptionClass: TClass);
- begin
- if Assigned(ExceptionClass) then
- begin
- if not Assigned(IgnoredExceptions) then
- IgnoredExceptions := TThreadList.Create;
- IgnoredExceptions.Add(ExceptionClass);
- end;
- end;
- procedure AddIgnoredExceptionByName(const AExceptionClassName: string);
- begin
- if AExceptionClassName <> '' then
- begin
- if not Assigned(IgnoredExceptionClassNamesCritSect) then
- IgnoredExceptionClassNamesCritSect := TJclCriticalSection.Create;
- if not Assigned(IgnoredExceptionClassNames) then
- begin
- IgnoredExceptionClassNames := TStringList.Create;
- IgnoredExceptionClassNames.Duplicates := dupIgnore;
- IgnoredExceptionClassNames.Sorted := True;
- end;
- IgnoredExceptionClassNamesCritSect.Enter;
- try
- IgnoredExceptionClassNames.Add(AExceptionClassName);
- finally
- IgnoredExceptionClassNamesCritSect.Leave;
- end;
- end;
- end;
- procedure RemoveIgnoredException(const ExceptionClass: TClass);
- var
- ClassList: TList;
- begin
- if Assigned(ExceptionClass) and Assigned(IgnoredExceptions) then
- begin
- ClassList := IgnoredExceptions.LockList;
- try
- ClassList.Remove(ExceptionClass);
- finally
- IgnoredExceptions.UnlockList;
- end;
- end;
- end;
- procedure RemoveIgnoredExceptionByName(const AExceptionClassName: string);
- var
- Index: Integer;
- begin
- if Assigned(IgnoredExceptionClassNames) and (AExceptionClassName <> '') then
- begin
- IgnoredExceptionClassNamesCritSect.Enter;
- try
- Index := IgnoredExceptionClassNames.IndexOf(AExceptionClassName);
- if Index <> -1 then
- IgnoredExceptionClassNames.Delete(Index);
- finally
- IgnoredExceptionClassNamesCritSect.Leave;
- end;
- end;
- end;
- function IsIgnoredException(const ExceptionClass: TClass): Boolean;
- var
- ClassList: TList;
- Index: Integer;
- begin
- Result := False;
- if Assigned(IgnoredExceptions) and not (stTraceAllExceptions in JclStackTrackingOptions) then
- begin
- ClassList := IgnoredExceptions.LockList;
- try
- for Index := 0 to ClassList.Count - 1 do
- if ExceptionClass.InheritsFrom(TClass(ClassList.Items[Index])) then
- begin
- Result := True;
- Break;
- end;
- finally
- IgnoredExceptions.UnlockList;
- end;
- end;
- if not Result and Assigned(IgnoredExceptionClassNames) and not (stTraceAllExceptions in JclStackTrackingOptions) then
- begin
- IgnoredExceptionClassNamesCritSect.Enter;
- try
- Result := IgnoredExceptionClassNames.IndexOf(ExceptionClass.ClassName) <> -1;
- if not Result then
- for Index := 0 to IgnoredExceptionClassNames.Count - 1 do
- if InheritsFromByName(ExceptionClass, IgnoredExceptionClassNames[Index]) then
- begin
- Result := True;
- Break;
- end;
- finally
- IgnoredExceptionClassNamesCritSect.Leave;
- end;
- end;
- end;
- procedure AddModule(const ModuleName: string);
- begin
- GlobalModulesList.AddModule(ModuleName);
- end;
- procedure DoExceptNotify(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean;
- BaseOfStack: Pointer);
- begin
- if (TrackingActiveCount > 0) and (not (stDisableIfDebuggerAttached in JclStackTrackingOptions) or (not IsDebuggerAttached)) and
- Assigned(ExceptObj) and (not IsIgnoredException(ExceptObj.ClassType)) and
- (not (stMainThreadOnly in JclStackTrackingOptions) or (GetCurrentThreadId = MainThreadID)) then
- begin
- if stStack in JclStackTrackingOptions then
- DoExceptionStackTrace(ExceptObj, ExceptAddr, OSException, BaseOfStack);
- if stExceptFrame in JclStackTrackingOptions then
- DoExceptFrameTrace;
- end;
- end;
- function JclStartExceptionTracking: Boolean;
- begin
- {Increment the tracking count only if exceptions are already being tracked or tracking can be started
- successfully.}
- if TrackingActiveCount = 0 then
- begin
- if JclHookExceptions and JclAddExceptNotifier(DoExceptNotify, npFirstChain) then
- begin
- TrackingActiveCount := 1;
- Result := True;
- end
- else
- Result := False;
- end
- else
- begin
- Inc(TrackingActiveCount);
- Result := False;
- end;
- end;
- function JclStopExceptionTracking: Boolean;
- begin
- {If the current tracking count is 1, an attempt is made to stop tracking exceptions. If successful the
- tracking count is set back to 0. If the current tracking count is > 1 it is simply decremented.}
- if TrackingActiveCount = 1 then
- begin
- Result := JclRemoveExceptNotifier(DoExceptNotify) and JclUnhookExceptions;
- if Result then
- Dec(TrackingActiveCount);
- end
- else
- begin
- if TrackingActiveCount > 0 then
- Dec(TrackingActiveCount);
- Result := False;
- end;
- end;
- function JclExceptionTrackingActive: Boolean;
- begin
- Result := TrackingActiveCount > 0;
- end;
- function JclTrackExceptionsFromLibraries: Boolean;
- begin
- Result := TrackingActiveCount > 0;
- if Result then
- JclInitializeLibrariesHookExcept;
- end;
- //=== Thread exception tracking support ======================================
- var
- RegisteredThreadList: TJclDebugThreadList;
- function JclDebugThreadList: TJclDebugThreadList;
- begin
- if RegisteredThreadList = nil then
- RegisteredThreadList := TJclDebugThreadList.Create;
- Result := RegisteredThreadList;
- end;
- type
- TKernel32_CreateThread = function(SecurityAttributes: Pointer; StackSize: LongWord;
- ThreadFunc: TThreadFunc; Parameter: Pointer;
- CreationFlags: LongWord; var ThreadId: LongWord): Integer; stdcall;
- TKernel32_ExitThread = procedure(ExitCode: Integer); stdcall;
- var
- ThreadsHooked: Boolean;
- Kernel32_CreateThread: TKernel32_CreateThread = nil;
- Kernel32_ExitThread: TKernel32_ExitThread = nil;
- function HookedCreateThread(SecurityAttributes: Pointer; StackSize: LongWord;
- ThreadFunc: TThreadFunc; Parameter: Pointer;
- CreationFlags: LongWord; ThreadId: PLongWord): Integer; stdcall;
- var
- LocalThreadId: LongWord;
- begin
- Result := Kernel32_CreateThread(SecurityAttributes, StackSize, ThreadFunc, Parameter, CreationFlags, LocalThreadId);
- if Result <> 0 then
- begin
- JclDebugThreadList.RegisterThreadID(LocalThreadId);
- if ThreadId <> nil then
- begin
- ThreadId^ := LocalThreadId;
- end;
- end;
- end;
- procedure HookedExitThread(ExitCode: Integer); stdcall;
- begin
- JclDebugThreadList.UnregisterThreadID(GetCurrentThreadID);
- Kernel32_ExitThread(ExitCode);
- end;
- function JclHookThreads: Boolean;
- var
- ProcAddrCache: Pointer;
- begin
- if not ThreadsHooked then
- begin
- ProcAddrCache := GetProcAddress(GetModuleHandle(kernel32), 'CreateThread');
- with TJclPeMapImgHooks do
- Result := ReplaceImport(SystemBase, kernel32, ProcAddrCache, @HookedCreateThread);
- if Result then
- begin
- @Kernel32_CreateThread := ProcAddrCache;
- ProcAddrCache := GetProcAddress(GetModuleHandle(kernel32), 'ExitThread');
- with TJclPeMapImgHooks do
- Result := ReplaceImport(SystemBase, kernel32, ProcAddrCache, @HookedExitThread);
- if Result then
- @Kernel32_ExitThread := ProcAddrCache
- else
- with TJclPeMapImgHooks do
- ReplaceImport(SystemBase, kernel32, @HookedCreateThread, @Kernel32_CreateThread);
- end;
- ThreadsHooked := Result;
- end
- else
- Result := True;
- end;
- function JclUnhookThreads: Boolean;
- begin
- if ThreadsHooked then
- begin
- with TJclPeMapImgHooks do
- begin
- ReplaceImport(SystemBase, kernel32, @HookedCreateThread, @Kernel32_CreateThread);
- ReplaceImport(SystemBase, kernel32, @HookedExitThread, @Kernel32_ExitThread);
- end;
- Result := True;
- ThreadsHooked := False;
- end
- else
- Result := True;
- end;
- function JclThreadsHooked: Boolean;
- begin
- Result := ThreadsHooked;
- end;
- //=== { TJclDebugThread } ====================================================
- constructor TJclDebugThread.Create(ASuspended: Boolean; const AThreadName: string);
- begin
- FThreadName := AThreadName;
- inherited Create(True);
- JclDebugThreadList.RegisterThread(Self, AThreadName);
- if not ASuspended then
- {$IFDEF RTL210_UP}
- Suspended := False;
- {$ELSE ~RTL210_UP}
- Resume;
- {$ENDIF ~RTL210_UP}
- end;
- destructor TJclDebugThread.Destroy;
- begin
- JclDebugThreadList.UnregisterThread(Self);
- inherited Destroy;
- end;
- procedure TJclDebugThread.DoHandleException;
- begin
- GlobalStackList.LockThreadID(ThreadID);
- try
- DoSyncHandleException;
- finally
- GlobalStackList.UnlockThreadID;
- end;
- end;
- procedure TJclDebugThread.DoNotify;
- begin
- JclDebugThreadList.DoSyncException(Self);
- end;
- procedure TJclDebugThread.DoSyncHandleException;
- begin
- // Note: JclLastExceptStackList and JclLastExceptFrameList returns information
- // for this Thread ID instead of MainThread ID here to allow use a common
- // exception handling routine easily.
- // Any other call of those JclLastXXX routines from another thread at the same
- // time will return expected information for current Thread ID.
- DoNotify;
- end;
- function TJclDebugThread.GetThreadInfo: string;
- begin
- Result := JclDebugThreadList.ThreadInfos[ThreadID];
- end;
- procedure TJclDebugThread.HandleException(Sender: TObject);
- begin
- FSyncException := Sender;
- try
- if not Assigned(FSyncException) then
- FSyncException := Exception(ExceptObject);
- if Assigned(FSyncException) and not IsIgnoredException(FSyncException.ClassType) then
- Synchronize(DoHandleException);
- finally
- FSyncException := nil;
- end;
- end;
- //=== { TJclDebugThreadList } ================================================
- type
- TThreadAccess = class(TThread);
- constructor TJclDebugThreadList.Create;
- begin
- FLock := TJclCriticalSection.Create;
- FReadLock := TJclCriticalSection.Create;
- FList := TObjectList.Create;
- FSaveCreationStack := False;
- end;
- destructor TJclDebugThreadList.Destroy;
- begin
- FreeAndNil(FList);
- FreeAndNil(FLock);
- FreeAndNil(FReadLock);
- inherited Destroy;
- end;
- function TJclDebugThreadList.AddStackListToLocationInfoList(ThreadID: DWORD; AList: TJclLocationInfoList): Boolean;
- var
- I: Integer;
- List: TJclStackInfoList;
- begin
- Result := False;
- FReadLock.Enter;
- try
- I := IndexOfThreadID(ThreadID);
- if (I <> -1) and Assigned(TJclDebugThreadInfo(FList[I]).StackList) then
- begin
- List := TJclDebugThreadInfo(FList[I]).StackList;
- AList.AddStackInfoList(List);
- Result := True;
- end;
- finally
- FReadLock.Leave;
- end;
- end;
- procedure TJclDebugThreadList.DoSyncException(Thread: TJclDebugThread);
- begin
- if Assigned(FOnSyncException) then
- FOnSyncException(Thread);
- end;
- procedure TJclDebugThreadList.DoSyncThreadRegistered;
- begin
- if Assigned(FOnThreadRegistered) then
- FOnThreadRegistered(FRegSyncThreadID);
- end;
- procedure TJclDebugThreadList.DoSyncThreadUnregistered;
- begin
- if Assigned(FOnThreadUnregistered) then
- FOnThreadUnregistered(FUnregSyncThreadID);
- end;
- procedure TJclDebugThreadList.DoThreadRegistered(Thread: TThread);
- begin
- if Assigned(FOnThreadRegistered) then
- begin
- FRegSyncThreadID := Thread.ThreadID;
- TThreadAccess(Thread).Synchronize(DoSyncThreadRegistered);
- end;
- end;
- procedure TJclDebugThreadList.DoThreadUnregistered(Thread: TThread);
- begin
- if Assigned(FOnThreadUnregistered) then
- begin
- FUnregSyncThreadID := Thread.ThreadID;
- TThreadAccess(Thread).Synchronize(DoSyncThreadUnregistered);
- end;
- end;
- function TJclDebugThreadList.GetThreadClassNames(ThreadID: DWORD): string;
- begin
- Result := GetThreadValues(ThreadID, 1);
- end;
- function TJclDebugThreadList.GetThreadCreationTime(ThreadID: DWORD): TDateTime;
- var
- I: Integer;
- begin
- FReadLock.Enter;
- try
- I := IndexOfThreadID(ThreadID);
- if I <> -1 then
- Result := TJclDebugThreadInfo(FList[I]).CreationTime
- else
- Result := 0;
- finally
- FReadLock.Leave;
- end;
- end;
- function TJclDebugThreadList.GetThreadIDCount: Integer;
- begin
- FReadLock.Enter;
- try
- Result := FList.Count;
- finally
- FReadLock.Leave;
- end;
- end;
- function TJclDebugThreadList.GetThreadHandle(Index: Integer): THandle;
- begin
- FReadLock.Enter;
- try
- Result := TJclDebugThreadInfo(FList[Index]).ThreadHandle;
- finally
- FReadLock.Leave;
- end;
- end;
- function TJclDebugThreadList.GetThreadID(Index: Integer): DWORD;
- begin
- FReadLock.Enter;
- try
- Result := TJclDebugThreadInfo(FList[Index]).ThreadID;
- finally
- FReadLock.Leave;
- end;
- end;
- function TJclDebugThreadList.GetThreadInfos(ThreadID: DWORD): string;
- begin
- Result := GetThreadValues(ThreadID, 2);
- end;
- function TJclDebugThreadList.GetThreadNames(ThreadID: DWORD): string;
- begin
- Result := GetThreadValues(ThreadID, 0);
- end;
- function TJclDebugThreadList.GetThreadParentID(ThreadID: DWORD): DWORD;
- var
- I: Integer;
- begin
- FReadLock.Enter;
- try
- I := IndexOfThreadID(ThreadID);
- if I <> -1 then
- Result := TJclDebugThreadInfo(FList[I]).ParentThreadID
- else
- Result := 0;
- finally
- FReadLock.Leave;
- end;
- end;
- function TJclDebugThreadList.GetThreadValues(ThreadID: DWORD; Index: Integer): string;
- var
- I: Integer;
- begin
- FReadLock.Enter;
- try
- I := IndexOfThreadID(ThreadID);
- if I <> -1 then
- begin
- case Index of
- 0:
- Result := TJclDebugThreadInfo(FList[I]).ThreadName;
- 1:
- Result := TJclDebugThreadInfo(FList[I]).ThreadClassName;
- 2:
- Result := Format('%.8x [%s] "%s"', [ThreadID, TJclDebugThreadInfo(FList[I]).ThreadClassName,
- TJclDebugThreadInfo(FList[I]).ThreadName]);
- end;
- end
- else
- Result := '';
- finally
- FReadLock.Leave;
- end;
- end;
- function TJclDebugThreadList.IndexOfThreadID(ThreadID: DWORD): Integer;
- var
- I: Integer;
- begin
- Result := -1;
- for I := FList.Count - 1 downto 0 do
- if TJclDebugThreadInfo(FList[I]).ThreadID = ThreadID then
- begin
- Result := I;
- Break;
- end;
- end;
- procedure TJclDebugThreadList.InternalRegisterThread(Thread: TThread; ThreadID: DWORD; const ThreadName: string);
- var
- I: Integer;
- ThreadInfo: TJclDebugThreadInfo;
- begin
- FLock.Enter;
- try
- I := IndexOfThreadID(ThreadID);
- if I = -1 then
- begin
- FReadLock.Enter;
- try
- FList.Add(TJclDebugThreadInfo.Create(GetCurrentThreadId, ThreadID, FSaveCreationStack));
- ThreadInfo := TJclDebugThreadInfo(FList.Last);
- if Assigned(Thread) then
- begin
- ThreadInfo.ThreadHandle := Thread.Handle;
- ThreadInfo.ThreadClassName := Thread.ClassName;
- end
- else
- begin
- ThreadInfo.ThreadHandle := 0;
- ThreadInfo.ThreadClassName := '';
- end;
- ThreadInfo.ThreadName := ThreadName;
- finally
- FReadLock.Leave;
- end;
- if Assigned(Thread) then
- DoThreadRegistered(Thread);
- end;
- finally
- FLock.Leave;
- end;
- end;
- procedure TJclDebugThreadList.InternalUnregisterThread(Thread: TThread; ThreadID: DWORD);
- var
- I: Integer;
- begin
- FLock.Enter;
- try
- I := IndexOfThreadID(ThreadID);
- if I <> -1 then
- begin
- if Assigned(Thread) then
- DoThreadUnregistered(Thread);
- FReadLock.Enter;
- try
- FList.Delete(I);
- finally
- FReadLock.Leave;
- end;
- end;
- finally
- FLock.Leave;
- end;
- end;
- procedure TJclDebugThreadList.RegisterThread(Thread: TThread; const ThreadName: string);
- begin
- InternalRegisterThread(Thread, Thread.ThreadID, ThreadName);
- end;
- procedure TJclDebugThreadList.RegisterThreadID(AThreadID: DWORD; const ThreadName: string);
- begin
- InternalRegisterThread(nil, AThreadID, ThreadName);
- end;
- procedure TJclDebugThreadList.UnregisterThread(Thread: TThread);
- begin
- InternalUnregisterThread(Thread, Thread.ThreadID);
- end;
- procedure TJclDebugThreadList.UnregisterThreadID(AThreadID: DWORD);
- begin
- InternalUnregisterThread(nil, AThreadID);
- end;
- //=== { TJclDebugThreadInfo } ================================================
- constructor TJclDebugThreadInfo.Create(AParentThreadID, AThreadID: DWORD; AStack: Boolean);
- begin
- FCreationTime := Now;
- FParentThreadID := AParentThreadID;
- try
- { TODO -oUSc : ... }
- // FStackList := JclCreateStackList(True, 0, nil, True);//probably IgnoreLevels = 11
- if AStack then
- FStackList := TJclStackInfoList.Create(True, 0, nil, True, nil, nil)
- else
- FStackList := nil;
- except
- FStackList := nil;
- end;
- FThreadID := AThreadID;
- end;
- destructor TJclDebugThreadInfo.Destroy;
- begin
- FStackList.Free;
- inherited Destroy;
- end;
- //=== { TJclCustomThreadInfo } ===============================================
- constructor TJclCustomThreadInfo.Create;
- var
- StackClass: TJclCustomLocationInfoListClass;
- begin
- inherited Create;
- StackClass := GetStackClass;
- FCreationTime := 0;
- FCreationStack := StackClass.Create;
- FName := '';
- FParentThreadID := 0;
- FStack := StackClass.Create;
- FThreadID := 0;
- FValues := [];
- end;
- destructor TJclCustomThreadInfo.Destroy;
- begin
- FCreationStack.Free;
- FStack.Free;
- inherited Destroy;
- end;
- procedure TJclCustomThreadInfo.AssignTo(Dest: TPersistent);
- begin
- if Dest is TJclCustomThreadInfo then
- begin
- TJclCustomThreadInfo(Dest).FCreationTime := FCreationTime;
- TJclCustomThreadInfo(Dest).FCreationStack.Assign(FCreationStack);
- TJclCustomThreadInfo(Dest).FName := FName;
- TJclCustomThreadInfo(Dest).FParentThreadID := FParentThreadID;
- TJclCustomThreadInfo(Dest).FStack.Assign(FStack);
- TJclCustomThreadInfo(Dest).FThreadID := FThreadID;
- TJclCustomThreadInfo(Dest).FValues := FValues;
- end
- else
- inherited AssignTo(Dest);
- end;
- function TJclCustomThreadInfo.GetStackClass: TJclCustomLocationInfoListClass;
- begin
- Result := TJclLocationInfoList;
- end;
- //=== { TJclThreadInfo } =====================================================
- procedure TJclThreadInfo.Fill(AThreadHandle: THandle; AThreadID: DWORD; AGatherOptions: TJclThreadInfoOptions);
- begin
- InternalFill(AThreadHandle, AThreadID, AGatherOptions, False);
- end;
- procedure TJclThreadInfo.FillFromExceptThread(AGatherOptions: TJclThreadInfoOptions);
- begin
- InternalFill(0, GetCurrentThreadID, AGatherOptions, True);
- end;
- function TJclThreadInfo.GetAsString: string;
- var
- ExceptInfo, ThreadName, ThreadInfoStr: string;
- begin
- if tioIsMainThread in Values then
- ThreadName := ' [MainThread]'
- else
- if tioName in Values then
- ThreadName := Name
- else
- ThreadName := '';
- ThreadInfoStr := '';
- if tioCreationTime in Values then
- ThreadInfoStr := ThreadInfoStr + Format(' CreationTime: %s', [DateTimeToStr(CreationTime)]);
- if tioParentThreadID in Values then
- ThreadInfoStr := ThreadInfoStr + Format(' ParentThreadID: %d', [ParentThreadID]);
- ExceptInfo := Format('ThreadID: %d%s%s', [ThreadID, ThreadName, ThreadInfoStr]) + #13#10;
- if tioStack in Values then
- ExceptInfo := ExceptInfo + Stack.AsString;
- if tioCreationStack in Values then
- ExceptInfo := ExceptInfo + 'Created at:' + #13#10 + CreationStack.AsString + #13#10;
- Result := ExceptInfo + #13#10;
- end;
- function TJclThreadInfo.GetStack(const AIndex: Integer): TJclLocationInfoList;
- begin
- case AIndex of
- 1: Result := TJclLocationInfoList(FCreationStack);
- 2: Result := TJclLocationInfoList(FStack);
- else
- Result := nil;
- end;
- end;
- function TJclThreadInfo.GetStackClass: TJclCustomLocationInfoListClass;
- begin
- Result := TJclLocationInfoList;
- end;
- procedure TJclThreadInfo.InternalFill(AThreadHandle: THandle; AThreadID: DWORD; AGatherOptions: TJclThreadInfoOptions; AExceptThread: Boolean);
- var
- Idx: Integer;
- List: TJclStackInfoList;
- begin
- if tioStack in AGatherOptions then
- begin
- if AExceptThread then
- List := JclLastExceptStackList
- else
- List := JclCreateThreadStackTrace(True, AThreadHandle);
- try
- Stack.AddStackInfoList(List);
- Values := Values + [tioStack];
- except
- { TODO -oUSc : ... }
- end;
- end;
- ThreadID := AThreadID;
- if tioIsMainThread in AGatherOptions then
- begin
- if MainThreadID = AThreadID then
- Values := Values + [tioIsMainThread];
- end;
- if AGatherOptions * [tioName, tioCreationTime, tioParentThreadID, tioCreationStack] <> [] then
- Idx := JclDebugThreadList.IndexOfThreadID(AThreadID)
- else
- Idx := -1;
- if (tioName in AGatherOptions) and (Idx <> -1) then
- begin
- Name := JclDebugThreadList.ThreadNames[AThreadID];
- Values := Values + [tioName];
- end;
- if (tioCreationTime in AGatherOptions) and (Idx <> -1) then
- begin
- CreationTime := JclDebugThreadList.ThreadCreationTime[AThreadID];
- Values := Values + [tioCreationTime];
- end;
- if (tioParentThreadID in AGatherOptions) and (Idx <> -1) then
- begin
- ParentThreadID := JclDebugThreadList.ThreadParentIDs[AThreadID];
- Values := Values + [tioParentThreadID];
- end;
- if (tioCreationStack in AGatherOptions) and (Idx <> -1) then
- begin
- try
- if JclDebugThreadList.AddStackListToLocationInfoList(AThreadID, CreationStack) then
- Values := Values + [tioCreationStack];
- except
- { TODO -oUSc : ... }
- end;
- end;
- end;
- //=== { TJclThreadInfoList } =================================================
- constructor TJclThreadInfoList.Create;
- begin
- inherited Create;
- FItems := TObjectList.Create;
- FGatherOptions := [tioIsMainThread, tioName, tioCreationTime, tioParentThreadID, tioStack, tioCreationStack];
- end;
- destructor TJclThreadInfoList.Destroy;
- begin
- FItems.Free;
- inherited Destroy;
- end;
- function TJclThreadInfoList.Add: TJclThreadInfo;
- begin
- FItems.Add(TJclThreadInfo.Create);
- Result := TJclThreadInfo(FItems.Last);
- end;
- procedure TJclThreadInfoList.AssignTo(Dest: TPersistent);
- var
- I: Integer;
- begin
- if Dest is TJclThreadInfoList then
- begin
- TJclThreadInfoList(Dest).Clear;
- for I := 0 to Count - 1 do
- TJclThreadInfoList(Dest).Add.Assign(Items[I]);
- TJclThreadInfoList(Dest).GatherOptions := FGatherOptions;
- end
- else
- inherited AssignTo(Dest);
- end;
- procedure TJclThreadInfoList.Clear;
- begin
- FItems.Clear;
- end;
- function TJclThreadInfoList.GetAsString: string;
- var
- I: Integer;
- begin
- Result := '';
- for I := 0 to Count - 1 do
- Result := Result + Items[I].AsString + #13#10;
- end;
- procedure TJclThreadInfoList.Gather(AExceptThreadID: DWORD);
- begin
- InternalGather([], [AExceptThreadID]);
- end;
- procedure TJclThreadInfoList.GatherExclude(AThreadIDs: array of DWORD);
- begin
- InternalGather([], AThreadIDs);
- end;
- procedure TJclThreadInfoList.GatherInclude(AThreadIDs: array of DWORD);
- begin
- InternalGather(AThreadIDs, []);
- end;
- function TJclThreadInfoList.GetCount: Integer;
- begin
- Result := FItems.Count;
- end;
- function TJclThreadInfoList.GetItems(AIndex: Integer): TJclThreadInfo;
- begin
- Result := TJclThreadInfo(FItems[AIndex]);
- end;
- procedure TJclThreadInfoList.InternalGather(AIncludeThreadIDs, AExcludeThreadIDs: array of DWORD);
- function OpenThread(ThreadID: DWORD): THandle;
- type
- TOpenThreadFunc = function(DesiredAccess: DWORD; InheritHandle: BOOL; ThreadID: DWORD): THandle; stdcall;
- const
- THREAD_SUSPEND_RESUME = $0002;
- THREAD_GET_CONTEXT = $0008;
- THREAD_QUERY_INFORMATION = $0040;
- var
- Kernel32Lib: THandle;
- OpenThreadFunc: TOpenThreadFunc;
- begin
- Result := 0;
- Kernel32Lib := GetModuleHandle(kernel32);
- if Kernel32Lib <> 0 then
- begin
- // OpenThread only exists since Windows ME
- OpenThreadFunc := GetProcAddress(Kernel32Lib, 'OpenThread');
- if Assigned(OpenThreadFunc) then
- Result := OpenThreadFunc(THREAD_SUSPEND_RESUME or THREAD_GET_CONTEXT or THREAD_QUERY_INFORMATION, False, ThreadID);
- end;
- end;
- function SearchThreadInArray(AThreadIDs: array of DWORD; AThreadID: DWORD): Boolean;
- var
- I: Integer;
- begin
- Result := False;
- if Length(AThreadIDs) > 0 then
- for I := Low(AThreadIDs) to High(AThreadIDs) do
- if AThreadIDs[I] = AThreadID then
- begin
- Result := True;
- Break;
- end;
- end;
- var
- SnapProcHandle: THandle;
- ThreadEntry: TThreadEntry32;
- NextThread: Boolean;
- ThreadIDList, ThreadHandleList: TList;
- I: Integer;
- PID, TID: DWORD;
- ThreadHandle: THandle;
- ThreadInfo: TJclThreadInfo;
- begin
- ThreadIDList := TList.Create;
- ThreadHandleList := TList.Create;
- try
- SnapProcHandle := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
- if SnapProcHandle <> INVALID_HANDLE_VALUE then
- try
- PID := GetCurrentProcessId;
- ThreadEntry.dwSize := SizeOf(ThreadEntry);
- NextThread := Thread32First(SnapProcHandle, ThreadEntry);
- while NextThread do
- begin
- if ThreadEntry.th32OwnerProcessID = PID then
- begin
- if SearchThreadInArray(AIncludeThreadIDs, ThreadEntry.th32ThreadID) or
- not SearchThreadInArray(AExcludeThreadIDs, ThreadEntry.th32ThreadID) then
- ThreadIDList.Add(Pointer(ThreadEntry.th32ThreadID));
- end;
- NextThread := Thread32Next(SnapProcHandle, ThreadEntry);
- end;
- finally
- CloseHandle(SnapProcHandle);
- end;
- for I := 0 to ThreadIDList.Count - 1 do
- begin
- ThreadHandle := OpenThread(TJclAddr(ThreadIDList[I]));
- ThreadHandleList.Add(Pointer(ThreadHandle));
- if ThreadHandle <> 0 then
- SuspendThread(ThreadHandle);
- end;
- try
- for I := 0 to ThreadIDList.Count - 1 do
- begin
- ThreadHandle := THandle(ThreadHandleList[I]);
- TID := TJclAddr(ThreadIDList[I]);
- ThreadInfo := Add;
- ThreadInfo.Fill(ThreadHandle, TID, FGatherOptions);
- end;
- finally
- for I := 0 to ThreadHandleList.Count - 1 do
- if ThreadHandleList[I] <> nil then
- begin
- ThreadHandle := THandle(ThreadHandleList[I]);
- ResumeThread(ThreadHandle);
- CloseHandle(ThreadHandle);
- end;
- end;
- finally
- ThreadIDList.Free;
- ThreadHandleList.Free;
- end;
- end;
- //== Miscellanuous ===========================================================
- {$IFDEF MSWINDOWS}
- {$IFNDEF WINSCP}
- function EnableCrashOnCtrlScroll(const Enable: Boolean): Boolean;
- const
- CrashCtrlScrollKey = 'SYSTEM\CurrentControlSet\Services\i8042prt\Parameters';
- CrashCtrlScrollName = 'CrashOnCtrlScroll';
- var
- Enabled: Integer;
- begin
- Enabled := 0;
- if Enable then
- Enabled := 1;
- RegWriteInteger(HKEY_LOCAL_MACHINE, CrashCtrlScrollKey, CrashCtrlScrollName, Enabled);
- Result := RegReadInteger(HKEY_LOCAL_MACHINE, CrashCtrlScrollKey, CrashCtrlScrollName) = Enabled;
- end;
- {$ENDIF ~WINSCP}
- function IsDebuggerAttached: Boolean;
- var
- IsDebuggerPresent: function: Boolean; stdcall;
- KernelHandle: THandle;
- P: Pointer;
- begin
- KernelHandle := GetModuleHandle(kernel32);
- @IsDebuggerPresent := GetProcAddress(KernelHandle, 'IsDebuggerPresent');
- if @IsDebuggerPresent <> nil then
- begin
- // Win98+ / NT4+
- Result := IsDebuggerPresent
- end
- else
- begin
- // Win9x uses thunk pointer outside the module when under a debugger
- P := GetProcAddress(KernelHandle, 'GetProcAddress');
- Result := TJclAddr(P) < KernelHandle;
- end;
- end;
- function IsHandleValid(Handle: THandle): Boolean;
- var
- Duplicate: THandle;
- Flags: DWORD;
- begin
- if IsWinNT then
- begin
- Flags := 0;
- Result := GetHandleInformation(Handle, Flags);
- end
- else
- Result := False;
- if not Result then
- begin
- // DuplicateHandle is used as an additional check for those object types not
- // supported by GetHandleInformation (e.g. according to the documentation,
- // GetHandleInformation doesn't support window stations and desktop although
- // tests show that it does). GetHandleInformation is tried first because its
- // much faster. Additionally GetHandleInformation is only supported on NT...
- Result := DuplicateHandle(GetCurrentProcess, Handle, GetCurrentProcess,
- @Duplicate, 0, False, DUPLICATE_SAME_ACCESS);
- if Result then
- Result := CloseHandle(Duplicate);
- end;
- end;
- {$ENDIF MSWINDOWS}
- {$IFDEF HAS_EXCEPTION_STACKTRACE}
- type
- PJclStackInfoRec = ^TJclStackInfoRec;
- TJclStackInfoRec = record
- Stack: TJclStackInfoList;
- Stacktrace: string;
- end;
- procedure ResolveStackInfoRec(Info: PJclStackInfoRec);
- var
- Str: TStringList;
- begin
- if (Info <> nil) and (Info.Stack <> nil) then
- begin
- Str := TStringList.Create;
- try
- Info.Stack.AddToStrings(Str,
- estoIncludeModuleName in JclExceptionStacktraceOptions,
- estoIncludeAdressOffset in JclExceptionStacktraceOptions,
- estoIncludeStartProcLineOffset in JclExceptionStacktraceOptions,
- estoIncludeVAddress in JclExceptionStacktraceOptions
- );
- FreeAndNil(Info.Stack);
- Info.Stacktrace := Str.Text;
- finally
- FreeAndNil(Str);
- end;
- end;
- end;
- procedure CleanUpStackInfo(Info: Pointer);
- begin
- if Info <> nil then
- begin
- PJclStackInfoRec(Info).Stack.Free;
- Dispose(PJclStackInfoRec(Info));
- end;
- end;
- {$STACKFRAMES ON}
- // We use the StackFrame's Base-Pointer to skip all local variables from this function
- function GetExceptionStackInfo(P: PExceptionRecord): Pointer;
- const
- cDelphiException = $0EEDFADE;
- cSetThreadNameException = $406D1388;
- var
- Stack: TJclStackInfoList;
- Info: PJclStackInfoRec;
- RawMode: Boolean;
- Delayed: Boolean;
- IgnoreLevels: Integer;
- begin
- if P^.ExceptionCode = cSetThreadNameException then
- begin
- Result := nil;
- Exit;
- end;
- RawMode := stRawMode in JclStackTrackingOptions;
- Delayed := stDelayedTrace in JclStackTrackingOptions;
- IgnoreLevels := 0;
- if RawMode then
- begin
- // Skip RaiseExceptionObject, System.@RaiseExcept and the function causing the exception.
- // The causing function is added again as the first stack item through P.ExceptionAddress.
- if (P.ExceptionAddress <> nil) and (P^.ExceptionCode = cDelphiException) then
- Inc(IgnoreLevels, 3)
- else
- Inc(IgnoreLevels, 2);
- end;
- if P^.ExceptionCode = cDelphiException then
- begin
- if (P^.ExceptObject <> nil) and (Exception(P.ExceptObject).StackInfo <> nil) then
- begin
- // This method is called twice for the same exception object if the user calls
- // AcquireExceptionObject and then throws this exception again. In this case the
- // StackInfo is already allocated and by overwriting it we produce a memory leak.
- // Example: "E := AcquireExceptionObject; raise E;"
- Result := Exception(P.ExceptObject).StackInfo;
- Exit;
- end;
- if (P^.ExceptObject <> nil) and
- not (stTraceAllExceptions in JclStackTrackingOptions) and
- IsIgnoredException(TObject(P^.ExceptObject).ClassType) then
- begin
- Result := nil;
- Exit;
- end;
- Stack := TJclStackInfoList.Create(RawMode, IgnoreLevels, P^.ExceptAddr, Delayed, GetFramePointer); // Don't add it to the GlobalStackList
- end
- else
- Stack := TJclStackInfoList.Create(RawMode, IgnoreLevels, P^.ExceptionAddress, Delayed, GetFramePointer); // Don't add it to the GlobalStackList
- New(Info);
- Info.Stack := Stack;
- if stImmediateExceptionStacktraceResolving in JclStackTrackingOptions then
- begin
- try
- ResolveStackInfoRec(Info);
- except
- CleanUpStackInfo(Info);
- Info := nil;
- end;
- end;
- Result := Info;
- end;
- {$IFDEF STACKFRAMES_ON}
- {$STACKFRAMES ON}
- {$ENDIF STACKFRAMES_ON}
- function GetStackInfoString(Info: Pointer): string;
- var
- Rec: PJclStackInfoRec;
- begin
- Rec := Info;
- if Rec <> nil then
- begin
- if Rec.Stack <> nil then
- ResolveStackInfoRec(Rec);
- Result := Rec.Stacktrace;
- end
- else
- Result := '';
- end;
- procedure SetupExceptionProcs;
- begin
- if not Assigned(Exception.GetExceptionStackInfoProc) then
- begin
- Exception.GetExceptionStackInfoProc := GetExceptionStackInfo;
- Exception.GetStackInfoStringProc := GetStackInfoString;
- Exception.CleanUpStackInfoProc := CleanUpStackInfo;
- end;
- end;
- procedure ResetExceptionProcs;
- begin
- if @Exception.GetExceptionStackInfoProc = @GetExceptionStackInfo then
- begin
- Exception.GetExceptionStackInfoProc := nil;
- Exception.GetStackInfoStringProc := nil;
- Exception.CleanUpStackInfoProc := nil;
- end;
- end;
- {$ENDIF HAS_EXCEPTION_STACKTRACE}
- procedure InitHexMap;
- var
- Ch: AnsiChar;
- begin
- FillChar(HexMap, SizeOf(HexMap), $80);
- for Ch := '0' to '9' do
- HexMap[Ch] := Ord(Ch) - Ord('0');
- for Ch := 'a' to 'f' do
- HexMap[Ch] := Ord(Ch) - (Ord('a') - 10);
- for Ch := 'A' to 'F' do
- HexMap[Ch] := Ord(Ch) - (Ord('A') - 10);
- end;
- procedure FreeJclDebugGlobals;
- begin
- {$IFDEF HAS_EXCEPTION_STACKTRACE}
- ResetExceptionProcs;
- {$ENDIF HAS_EXCEPTION_STACKTRACE}
- FreeAndNil(RegisteredThreadList);
- FreeAndNil(DebugInfoList);
- FreeAndNil(GlobalStackList);
- FreeAndNil(GlobalModulesList);
- FreeAndNil(DebugInfoCritSect);
- FreeAndNil(InfoSourceClassList);
- FreeAndNil(IgnoredExceptions);
- FreeAndNil(IgnoredExceptionClassNames);
- FreeAndNil(IgnoredExceptionClassNamesCritSect);
- TJclDebugInfoSymbols.CleanupDebugSymbols;
- end;
- initialization
- InitHexMap;
- DebugInfoCritSect := TJclCriticalSection.Create;
- GlobalModulesList := TJclGlobalModulesList.Create;
- GlobalStackList := TJclGlobalStackList.Create;
- AddIgnoredException(EAbort);
- {$IFDEF UNITVERSIONING}
- RegisterUnitVersion(HInstance, UnitVersioning);
- {$ENDIF UNITVERSIONING}
- {$IFDEF HAS_EXCEPTION_STACKTRACE}
- SetupExceptionProcs;
- {$ENDIF HAS_EXCEPTION_STACKTRACE}
- finalization
- {$IFDEF UNITVERSIONING}
- UnregisterUnitVersion(HInstance);
- {$ENDIF UNITVERSIONING}
- { TODO -oPV -cInvestigate : Calling JclStopExceptionTracking causes linking of various classes to
- the code without a real need. Although there doesn't seem to be a way to unhook exceptions
- safely because we need to be covered by JclHookExcept.Notifiers critical section }
- JclStopExceptionTracking;
- GlobalStackList.Clear;
- JclDebugFinalized := True;
- if GlobalStackListLiveCount = 0 then
- FreeJclDebugGlobals;
- end.
|