rflags নামে একটি বিশেষ রেজিস্টার আছে।
এর বিটগুলো নানা শর্তের জন্য ফ্ল্যাগের মতো কাজ করে।
এর মধ্যে কয়েকটি নিচে দেওয়া হলো:
| name | symbol | bit |
|---|---|---|
| ক্যারি | CF | 0 |
| জিরো | ZF | 6 |
| সাইন | SF | 7 |
| ওভারফ্লো | OF | 11 |
সম্পূর্ণ তালিকার জন্য Intel-এর ম্যানুয়াল দেখুন।
rflags-এর ফ্ল্যাগগুলো সরাসরি পরিবর্তন করা হয় না।
বরং এগুলো অনেক আলাদা আলাদা ইন্সট্রাকশন দ্বারা সেট হয়।
যেমন, অনেক অ্যারিথমেটিক বা বিটওয়াইজ অপারেশনে ফলাফল শূন্য হলে ZF সেট হয়।
শর্ত পরীক্ষা করার জন্য সবচেয়ে বেশি ব্যবহৃত ইন্সট্রাকশনগুলোর একটি হলো cmp।
এটি দুটি অপারেন্ড নেয় এবং ফ্ল্যাগগুলো হালনাগাদ করে, কিন্তু নিজের অপারেন্ডগুলো পরিবর্তন করে না।
cmp ইন্সট্রাকশনটি প্রথম অপারেন্ড থেকে দ্বিতীয় অপারেন্ড বিয়োগ করে এবং ফলাফল অনুযায়ী ফ্ল্যাগ সেট করে।
যদি A হয় প্রথম অপারেন্ড আর B হয় দ্বিতীয়টি:
| ফ্ল্যাগ | কখন সেট হয় |
|---|---|
| CF | A < B (আনসাইনড) |
| ZF | A == B |
| SF | A < B (সাইনড, ওভারফ্লো নেই) |
| OF | সাইনড বিয়োগে ওভারফ্লো |
ডিফল্টভাবে x86-64-এর কোড উপর থেকে নিচে ক্রমানুসারে এক্সিকিউট হয়।
কিন্তু এমন অনেক পরিস্থিতি আছে যেখানে এই আচরণ বদলানো দরকার হয়। যেমন, কোনো শর্তের ভিত্তিতে ভিন্ন এক সেট ইন্সট্রাকশন এক্সিকিউট করা।
উচ্চস্তরের প্রোগ্রামিং ভাষায় এটি সাধারণত if...else কন্ডিশনালের মতো অ্যাবস্ট্রাকশন দিয়ে করা হয়।
কিন্তু x86-64-এ এসব নেই।
বরং x86-64 এমন ইন্সট্রাকশন দেয় যেগুলো কার্যকরভাবে এক্সিকিউশনকে কোডের আরেক জায়গায় স্থানান্তর করে।
এটাকে বলা হয় branching।
এমন দুটি ইন্সট্রাকশন আমরা আগেই দেখেছি: call আর ret।
কোনো ফাংশন কল করা হলে এক্সিকিউশন কলার থেকে কল করা ফাংশনে স্থানান্তরিত হয়। আর ফিরে আসার সময় এক্সিকিউশন আবার কলারে স্থানান্তরিত হয়।
যদি কোনো ret না থাকে, তবে এক্সিকিউশন এক ফাংশন থেকে পরের ফাংশনে ফলথ্রু করে।
এটা কখনো কখনো কোড ফ্লো অপটিমাইজ করতে ব্যবহার করা যায়।
jmp ইন্সট্রাকশনটি শর্তহীনভাবে প্রোগ্রামের এক্সিকিউশনকে কোডের আরেক বিন্দুতে স্থানান্তর করে। এর একমাত্র অপারেন্ডটি একটি লেবেল, যেটিতে সেই বিন্দুর অ্যাড্রেস থাকে যেখান থেকে এক্সিকিউশন চলতে থাকবে।
যেমন, নিচের ফাংশনটি দেখুন:
fn:
mov rax, 5
jmp end
add rax, 10
end:
ret
fn কল করা হলে এক্সিকিউশন শুরু হয় mov rax, 5 থেকে।
এতে ওই মুহূর্তে rax-এর মান 5 হয়ে যায়।
পরের ইন্সট্রাকশনটি jmp end, যা এক্সিকিউশনকে end লেবেলে স্থানান্তর করে।
end-এর পরের ইন্সট্রাকশনটি ret, যা এক্সিকিউশনকে আবার কলার ফাংশনে ফিরিয়ে দেয়।
লক্ষ্য করুন, add rax, 10 যেহেতু jmp end-এর পরে ও end-এর আগে আছে, তাই এটি কখনো এক্সিকিউট হয় না।
fn রিটার্ন করার সময় rax-এর মান হয় 5।
jcc ইন্সট্রাকশন পরিবারটি কেবল নির্দিষ্ট কোনো শর্ত পূরণ হলেই প্রোগ্রামের এক্সিকিউশনকে আরেক বিন্দুতে স্থানান্তর করে। নাহলে এক্সিকিউশন ক্রমানুসারে চলতে থাকে।
প্রতিটি শর্ত rflags-এর এক বা একাধিক ফ্ল্যাগের সাথে মিলে যায়।
কিছু jcc ভ্যারিয়েন্ট পরীক্ষা করে ফ্ল্যাগ সেট কি না, আবার কিছু পরীক্ষা করে ফ্ল্যাগ ক্লিয়ার কি না।
jcc-এর cc আক্ষরিক কিছু নয়, বরং পরীক্ষিত ফ্ল্যাগের সাথে যুক্ত নির্দিষ্ট সাফিক্সকে বোঝায়।
সাফিক্স অনেক আছে, আর তাদের অনেকগুলোই অন্য একটি সাফিক্সের মতোই একই শর্ত পরীক্ষা করে। এর কিছু সরাসরি একটি ফ্ল্যাগকে বোঝায়, ফলে নির্দিষ্ট ফ্ল্যাগটি সেট থাকলে ইন্সট্রাকশনটি একটি লেবেলে জাম্প করে:
| সাফিক্স | কখন জাম্প করে |
|---|---|
| z | ZF == 1 |
| c | CF == 1 |
| s | SF == 1 |
| o | OF == 1 |
আরও অনেক সাফিক্স বেছে নেওয়া হয়েছে cmp ইন্সট্রাকশনে তাদের অর্থ বোঝানোর জন্য।
যেমন:
| ইন্সট্রাকশন | সাফিক্স | কখন জাম্প করে |
|---|---|---|
| cmp A, B | e | A == B |
| cmp A, B | l | A < B (সাইনড) |
| cmp A, B | b | A < B (আনসাইনড) |
| cmp A, B | g | A > B (সাইনড) |
| cmp A, B | a | A > B (আনসাইনড) |
শর্তে সমতা অন্তর্ভুক্ত করতে l, b, g বা a-এর পরে e যোগ করা যায়:
cmp rcx, r8
jge two ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison
সব সাফিক্সের জন্যই উল্টো আচরণ পরীক্ষা করার ভ্যারিয়েন্ট আছে।
সেগুলোর সিনট্যাক্স একই, শুধু সাফিক্সের আগে একটা n থাকে।
যেমন, ZF সেট না থাকলে jnz জাম্প করে।
একইভাবে, A না >= B হলে (A ও B-কে আনসাইনড ইন্টিজার হিসেবে ব্যাখ্যা করা হয়) jnae জাম্প করে।
কিছু সাফিক্স একই শর্তের অ্যালিয়াস।
যেমন, jz (সাফিক্স z, ZF-এর জন্য) আর je (সাফিক্স e, সমতার জন্য), দুটোই ZF সেট থাকলে জাম্প করে।
কারণ cmp-এ বিয়োগের ফল শূন্য হলে ZF সেট হয়, আর সেটা দুটি অপারেন্ড সমান হওয়াকেই বোঝায়।
তবে অন্য কিছু সাফিক্স ফ্ল্যাগের সমন্বয় পরীক্ষা করে, আর সেগুলোকে একটি একক ফ্ল্যাগ-সাফিক্স দিয়ে সরাসরি প্রতিস্থাপন করা যায় না।
আপনার তুলনার সিম্যান্টিক্স যেটি ভালোভাবে বোঝায়, সেই সাফিক্সটিই বেছে নিন।
লেবেলগুলো পুরো সোর্স ফাইলে দৃশ্যমান, এগুলো কোনো ফাংশনে সীমাবদ্ধ নয়। তাই একই লেবেলের নাম আবার ব্যবহার করা সম্ভব নয়।
লোকাল লেবেলের আচরণ নকল করার জন্য NASM-এ একটি বিশেষ নোটেশন আছে: যে লেবেলের আগে একটা পিরিয়ড (.) বসিয়ে ঘোষণা করা হয়।
এই নোটেশনটি এমন একটি লেবেল তৈরি করে, যেটি নিজের মধ্যে আগের ডটবিহীন লেবেলের নামটি অন্তর্ভুক্ত করে রাখে:
section .text
fn1:
...
.example: ; this is fn1.example
...
ret
fn2:
...
.example: ; this is fn2.example
...
ret
তবুও কোডের যেকোনো জায়গা থেকে পুরো লেবেলের নাম ব্যবহার করে এই লেবেলে জাম্প করা যায়, যেমন jmp fn1.example।
তবে লেবেলের ডট থেকে শুরু হওয়া অংশটি ব্যবহার করে করা জাম্পটি হবে উপরের ফাংশনের ভেতরের লেবেলে।
যেমন, .example এমনভাবে কাজ করে যেন এটি ফাংশনটির মধ্যেই সীমাবদ্ধ:
section .text
fn1:
...
.example:
...
jmp .example ; this jumps to fn1.example
fn2:
...
.example:
...
jmp .example ; this jumps to fn2.example
মনে রাখবেন, কোনো ফাংশনের ভেতরে ডটবিহীন একটি লেবেল আসলে আরেকটি ফাংশনই তৈরি করে:
section .text
fn1:
...
.example: ; this is 'fn1.example'
...
non_dotted:
...
.example: ; this is 'non_dotted.example'
...
ret