- C/C++ pointers के साथ AllocationRecord metadata जोड़कर उन्हें ट्रैक करने और dereference के समय memory boundary checks करने वाली संरचना
- pointer assignment, arithmetic, function argument passing, return, और
malloc·free calls तक मूल pointer value के साथ संबंधित metadata को भी साथ ले जाना, या उन्हें Fil-C-विशिष्ट calls में बदलने का तरीका
- heap memory के भीतर pointers का metadata invisible_bytes में अलग से स्टोर किया जाता है, और pointer load·store के समय value और metadata दोनों को साथ पढ़ा-लिखा जाता है, साथ ही alignment checks भी लागू होते हैं
filc_free केवल visible_bytes और invisible_bytes को free करता है, AllocationRecord स्वयं बना रहता है, और बाद की सफाई garbage collector संभालता है; जिन local variables के address के escape होने की संभावना हो उन्हें heap promotion किया जाता है
- threads, function pointers, memory·performance optimizations जैसी वास्तविक implementation complexities अभी भी बची हैं, लेकिन बड़े C/C++ codebases की memory safety verification या pointer provenance के ठोस system example के रूप में इसका उपयोग संभव है
Fil-C का सरलीकृत मॉडल
- Fil-C C/C++ code को memory-safe तरीके से संभालने के लिए pointers के साथ AllocationRecord* metadata ट्रैक करने वाली संरचना का उपयोग करता है
- वास्तविक implementation LLVM IR rewriting पर आधारित है, लेकिन यह simplified model C/C++ source code को automatic transformation के रूप में दिखाता है
- हर function के pointer-type local variable के लिए एक corresponding
AllocationRecord* local variable जोड़ा जाता है
- उदाहरण के लिए
T1* p1 के लिए AllocationRecord* p1ar = NULL जोड़ा जाता है
- pointer local variables पर simple assignment और calculation में मूल pointer value के साथ AllocationRecord* भी साथ चलता है
p1 = p2 को p1 = p2, p1ar = p2ar में बदला जाता है
p1 = p2 + 10 में भी p1ar = p2ar साथ आता है
- integer से pointer में cast करने पर metadata को
NULL सेट किया जाता है
- pointer को integer में बदलने वाला cast ज्यों का त्यों रहता है
- function argument passing और return में भी pointer के साथ अतिरिक्त AllocationRecord* पास किया जाता है, और कुछ standard library calls को Fil-C-विशिष्ट functions से बदला जाता है
malloc और free calls को क्रमशः filc_malloc, filc_free में बदला जाता है
- उदाहरण के लिए
p1 = malloc(x); free(p1); को {p1, p1ar} = filc_malloc(x); filc_free(p1, p1ar); में बदला जाता है
filc_malloc केवल requested memory allocate नहीं करता, बल्कि तीन allocations करता है
AllocationRecord object allocation
- वास्तविक data के लिए
visible_bytes allocation
- अदृश्य metadata storage के लिए
invisible_bytes को calloc से allocate करना
AllocationRecord में visible_bytes, invisible_bytes, length fields होते हैं
Dereference और boundary checks
- pointer dereference के समय साथ मौजूद AllocationRecord* का उपयोग करके boundary checks किए जाते हैं
- जाँचा जाता है कि pointer metadata
NULL न हो
- वर्तमान pointer position और
visible_bytes के starting address का अंतर निकाला जाता है
- देखा जाता है कि offset कुल length से छोटा है या नहीं
- बची हुई length dereference target के size के लिए पर्याप्त है या नहीं
- read और write दोनों पर यही checking process लागू होती है
x = *p1 से पहले भी check किया जाता है
*p2 = x से पहले भी यही check लागू होता है
- इस संरचना से pointer जिस target को दिखा रहा है, उस पर allocation range के बाहर access रोकी जाती है
Heap के अंदर pointers और invisible_bytes
- heap memory में stored pointers को compiler local variables की तरह सीधे अलग variables से manage नहीं कर सकता, इसलिए invisible_bytes का उपयोग होता है
- अगर
visible_bytes + i location पर pointer है, तो उसका corresponding AllocationRecord* invisible_bytes + i location पर store किया जाता है
- यानी
invisible_bytes ऐसा व्यवहार करता है मानो वह AllocationRecord* element type वाला array हो
- memory से pointer value पढ़ते या लिखते समय सामान्य boundary check के अलावा alignment check भी जोड़ा जाता है
- offset
i का sizeof(AllocationRecord*) का multiple होना जाँचा जाता है
- तभी
invisible_bytes को AllocationRecord** array की तरह सुरक्षित रूप से access किया जा सकता है
- pointer load के समय data pointer के साथ metadata भी साथ load होता है
p2 = *p1 में p2 = *p1 के बाद p2ar = *(AllocationRecord**)(p1ar->invisible_bytes + i) जोड़ा जाता है
- pointer store के समय pointer value के साथ उसका corresponding metadata भी store किया जाता है
*p1 = p2 में वास्तविक data store करने के बाद *(AllocationRecord**)(p1ar->invisible_bytes + i) = p2ar किया जाता है
filc_free और garbage collector
filc_free pointer के NULL न होने पर AllocationRecord के साथ consistency check के बाद केवल दो memory regions free करता है
par != NULL की जाँच
p == par->visible_bytes की जाँच
visible_bytes और invisible_bytes को free करना
- इसके बाद
visible_bytes, invisible_bytes को NULL और length को 0 में बदलना
filc_malloc तीन allocations करता है, लेकिन filc_free AllocationRecord object को स्वयं free नहीं करता
- इस अंतर को garbage collector संभालता है
- इस simplified model में stop-the-world GC पर्याप्त है, जबकि वास्तविक Fil-C parallel concurrent incremental collector का उपयोग करता है
- GC
AllocationRecord objects को follow करते हुए tracing करता है
- unreachable
AllocationRecord को free के लिए target बनाता है
- GC अतिरिक्त रूप से दो और काम करता है
- unreachable
AllocationRecord को free करते समय filc_free call करना
length 0 वाले AllocationRecord को point करने वाले सभी pointers को length 0 वाले एक single canonical AllocationRecord में बदलना
- इस व्यवहार से
free न बुलाने पर भी memory leak नहीं होती
- GC automatic free करता है
- हालांकि
free call करने से GC से पहले memory को जल्दी release करना संभव होता है
free के बाद संबंधित AllocationRecord अंततः unreachable हो जाता है और बाद में साफ किया जा सकता है
Local variable address escape और heap promotion
- GC मौजूद होने पर local variables के addresses को सुरक्षित रूप से संभालने की सीमा बढ़ जाती है
- यदि किसी local variable का address लिया गया है और compiler यह साबित नहीं कर सकता कि वह address variable की lifetime के बाहर escape नहीं करेगा, तो उसे heap allocation में promote किया जाता है
- ऐसे local variables stack के बजाय
malloc के जरिए allocate होते हैं
- उनके लिए अलग से
free insert करने की जरूरत नहीं होती
- GC उनकी सफाई संभालता है
Fil-C version का memmove
- C standard library का
memmove arbitrary memory को संभालता है, इसलिए compiler को उसके भीतर pointers हैं या नहीं, यह पता नहीं होता
- इसके लिए heuristic लागू की जाती है
- arbitrary memory के भीतर pointer पूरी तरह उसी memory range के अंदर पूर्ण रूप से शामिल होना चाहिए
- pointer सही तरह aligned होना चाहिए
- इसी नियम की वजह से समान 8-byte move में भी व्यवहार अलग हो सकता है
- aligned 8 bytes को एक बार में
memmove करने पर संबंधित range के invisible_bytes भी साथ move होते हैं
- अगर 1 byte करके 8 बार
memmove किया जाए, तो invisible_bytes move नहीं होते
वास्तविक implementation में जुड़ने वाली अतिरिक्त जटिलताएँ
-
Threads
- concurrency, GC complexity बढ़ाने वाला तत्व है
filc_free तुरंत memory free नहीं कर सकता
- क्योंकि free करने वाला thread और उसी memory को access करने वाला दूसरा thread race condition में हो सकते हैं
- pointers पर atomic operations के लिए भी अतिरिक्त handling चाहिए
- मूल rewriting pointer load/store को दो load/store में बदल देती है, जिससे atomicity टूट जाती है
-
Function pointers
AllocationRecord के अतिरिक्त metadata से यह चिह्नित किया जाता है कि visible_bytes सामान्य data नहीं बल्कि executable code pointer है
- function pointer
p1 के जरिए call करते समय p1 == p1ar->visible_bytes की जाँच और साथ ही यह verify किया जाता है कि p1ar function pointer को represent करता है
- function pointer पर type confusion attacks को रोकने के लिए calling ABI में भी type signature verification चाहिए
- एक तरीका यह है कि सभी functions की type signature समान बना दी जाए
- जैसे सभी arguments को struct में रखकर memory के जरिए pass किया जाए
- ABI boundary पर हर function उस struct से संबंधित केवल एक
AllocationRecord प्राप्त करे
-
Memory usage optimization
- यह विचार किया जा सकता है कि
filc_malloc invisible_bytes को तुरंत allocate न करे, बल्कि जरूरत पड़ने पर lazy allocation करे
AllocationRecord और visible_bytes को एक ही allocation में साथ रखने का तरीका भी संभव है
- यदि underlying
malloc हर allocation के सामने metadata जोड़ता है, तो उस metadata को AllocationRecord में रखने पर भी विचार किया जा सकता है
-
Performance optimization
- Fil-C की memory safety के साथ performance cost जुड़ा हुआ है
- खोई हुई performance को आंशिक रूप से वापस पाने के लिए कई techniques लागू की जा सकती हैं
Fil-C के उपयोग के अवसर
- बड़े C/C++ codebases जो ऊपर से चलते हुए दिखते हैं, लेकिन जिनमें memory safety verification नहीं है, और memory safety के लिए GC तथा बड़े performance overhead को स्वीकार किया जा सकता है, वहाँ इसका उपयोग संभव है
- Java, Go, Rust में rewrite करने से पहले इसे एक अस्थायी उपाय के रूप में देखा जा सकता है
- ASan की तरह memory bug detection के उद्देश्य से भी Fil-C चलाया जा सकता है
- C/C++ code को Fil-C के नीचे चलाकर memory bugs देखे जा सकते हैं
- जिन भाषाओं में compile-time language और runtime language समान हों और compile-time safety मजबूत हो, वहाँ safe compile-time evaluation के लिए इसका उपयोग संभव है
- उदाहरण के रूप में Zig का उल्लेख है
- भले runtime evaluation safe न हो, compile-time evaluation में Fil-C जैसी संरचना उपयोगी हो सकती है
- pointer provenance को संभालने वाले एक ठोस system case के रूप में भी इसका महत्व है
- जब
p1 और p2 का type समान हो, तो if (p1 == p2) { f(p1); } को if (p1 == p2) { f(p2); } में बदलने जैसी optimization संभव है या नहीं, यह प्रश्न उठता है
- Fil-C में
f को pass किया जाने वाला AllocationRecord* अलग हो सकता है, इसलिए उत्तर स्पष्ट रूप से नहीं है
- इस अर्थ में Fil-C pointer provenance वाले एक ठोस system example की भूमिका निभाता है
अभी कोई टिप्पणी नहीं है.