Stack Frame
স্ট্যাক ফ্রেম
একটা function call-এর জন্য stack-এ বরাদ্দ মেমরি-অঞ্চল — return address, saved rbp, local variable — সবই rbp-র সাপেক্ষে ঠিকানা করা।
also: prologue, epilogue
Stack নিচের দিকে বাড়ে (উচ্চ ঠিকানা থেকে নিম্ন) — push-এ rsp
কমে, pop-এ বাড়ে। call return address push করে জাম্প করে
(atomic, দুই মাইক্রো-ধাপ একসাথে); ret সেটা pop করে rip-এ বসায়।
Prologue (frame স্থাপন) — প্রতিটা ধাপ কারণ-সহ:
push %rbp ; rbp callee-saved, ব্যবহারের আগে বাঁচাও
mov %rsp, %rbp ; স্থির reference-point স্থাপন (rsp নড়তে থাকবে, rbp না)
sub $N, %rsp ; locals-এর জন্য N byte দখল করো
Epilogue — ঠিক বিপরীত ক্রমে undo:
leave ; ≡ mov %rbp,%rsp; pop %rbp
ret ; return address pop → rip
Layout (rbp-র সাপেক্ষে, উচ্চ থেকে নিম্ন):
rbp+8 → return address
rbp+0 → saved rbp (rbp এখানে)
rbp-8 → স্থানীয় ভেরিয়েবল ১
rbp-16 → স্থানীয় ভেরিয়েবল ২ (rsp এখানে হতে পারে)
Buffer overflow-র উৎস ঠিক এখানেই। Local buffer (char buf[8])-এর সীমা কোনো hardware/runtime এনফোর্স করে না — সীমা
ছাড়িয়ে লেখা মানে ঠিক পরের ঠিকানায় লেখা, যেটা হতে পারে saved
rbp, তারপর return address। ret অন্ধভাবে বিশ্বাস করে top-এ
বৈধ ঠিকানা আছে — ওভাররাইট হলে ভুল জায়গায় জাম্প করে, সাধারণত crash।
প্রতিরক্ষা: stack canary (ফেরার আগে যাচাই), ASLR (ঠিকানা
random), NX bit (stack non-executable) — কোনোটাই একা নিশ্ছিদ্র
না, একসাথে “defense in depth”।
-fomit-frame-pointer-এ rbp সাধারণ register হিসেবে মুক্ত
হয়, locals rsp-র সাপেক্ষে (variable offset) address হয় —
debugger তখন frame-pointer-chain না, আলাদা DWARF metadata দিয়ে
stack unwind করে।