একটি ইন্টিজারের প্রতিটি বিট একটি বাইনারি মান সংরক্ষণ করতে ব্যবহার করা যায়। যেহেতু অনেক পরিস্থিতিতেই বাইনারি তথ্য জড়িত থাকে, যেমন ট্রু বা ফলস, অন্তর্ভুক্তি বা বর্জন, অন বা অফ, তাই একটি N-বিট ইন্টিজারের বাইনারি রূপ N সংখ্যক আইটেমের বাইনারি অবস্থা এনকোড করার একটি সংক্ষিপ্ত উপায় দেয়। এ কারণেই অ্যাসেম্বলিতে বিট ও বাইট ম্যানিপুলেট করার সামর্থ্য অপরিহার্য হয়ে ওঠে। x86-64 ইন্সট্রাকশন সেট অনেক রকমের বিটওয়াইজ ম্যানিপুলেশন ইন্সট্রাকশন সরবরাহ করে।
এই ইন্সট্রাকশনগুলো একটি অপারেন্ডের একক বিট নিয়ে কাজ করে।
এগুলো সবই দুটি অপারেন্ড নেয়, দ্বিতীয়টি প্রথম অপারেন্ডে যে বিটটি নিয়ে কাজ করা হচ্ছে তার ইনডেক্স নির্দেশ করে। এগুলোর সবই নির্বাচিত বিটটি ক্যারি ফ্ল্যাগ (CF)-এ কপি করে।
| নাম | বিবরণ |
|---|---|
bt |
কোনো অপারেন্ড পরিবর্তন না করেই বিটটি CF-এ কপি করে |
bts |
বিটটি CF-এ কপি করে এবং ডেস্টিনেশন অপারেন্ডে সেট করে |
btr |
বিটটি CF-এ কপি করে এবং ডেস্টিনেশন অপারেন্ডে ক্লিয়ার করে |
btc |
বিটটি CF-এ কপি করে এবং ডেস্টিনেশন অপারেন্ডে কমপ্লিমেন্ট (ফ্লিপ) করে |
একটি অপারেন্ডের সব বিটে বিটওয়াইজ অপারেশন করা হয়।
এগুলোর প্রত্যেকটির জন্য সম্পাদিত বিটওয়াইজ অপারেশনের মতোই একই নামের একটি ইন্সট্রাকশন আছে:
| নাম | বিবরণ |
|---|---|
and |
দুটি বিটই 1 হলে 1 |
or |
অন্তত একটি বিট 1 হলে 1 |
xor |
বিট দুটি ভিন্ন হলে 1 |
not |
বিট 0 হলে 1; বিট 1 হলে 0 |
এগুলোর বেশিরভাগই দুটি অপারেন্ড নেয়, দুটির উপরই একটি বিটওয়াইজ অপারেশন করে এবং ফলাফলটি ডেস্টিনেশন অপারেন্ডে সংরক্ষণ করে।
ব্যতিক্রম হলো not, যা শুধু একটি ডেস্টিনেশন অপারেন্ড নেয়।
আমরা যখন এককে অন্তর্ভুক্তি আর শূন্যকে বর্জন হিসেবে ব্যাখ্যা করি, তখন একটি ইন্টিজারকে বিটমাস্ক (বা সহজভাবে মাস্ক) বলা হয়।
একটি বিটমাস্ক আইটেমগুলোকে "মাস্ক আউট" করে, কারণ i-তম বিটে শূন্য থাকলে i-তম আইটেমটি বাদ পড়ে, আর এক থাকলে সেটি অন্তর্ভুক্ত হয়।
আমরা সাধারণত একটি বিটমাস্ক ব্যবহার করে একটি ইন্টিজারের নির্দিষ্ট কিছু বিট অন্তর্ভুক্ত করি আর বাকিগুলো বাদ দিই।
উদাহরণস্বরূপ, ধরা যাক A এমন একটি ইন্টিজার যার বাইনারি রূপ:
| index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| bits | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
আবার, ধরা যাক M এমন একটি ইন্টিজার যার বাইনারি রূপ:
| index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| bits | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
দুটোই 8-বিট ইন্টিজার।
এক্ষেত্রে বলা যায়, M A-এর বিট 0, 2 এবং 3 নির্বাচন করে, আর বাকিগুলো বাদ দেয়।
আগে আলোচনা করা বিটওয়াইজ ইন্সট্রাকশনগুলো মাস্ক দিয়ে ইন্টিজার ম্যানিপুলেট করার সময় কাজে লাগে। যেমন:
M যে বিটগুলো নির্বাচন করে না, A-এর সেগুলো ক্লিয়ার করতে বিটওয়াইজ AND নিন: A AND M.M যে বিটগুলো নির্বাচন করে, A-এর সেগুলো সেট করতে বিটওয়াইজ OR নিন: A OR M.test ইন্সট্রাকশন দুটি অপারেন্ডের মধ্যে বিটওয়াইজ AND করে এবং ফলাফল অনুযায়ী ফ্ল্যাগ সেট করে।
যদি A প্রথম অপারেন্ড হয় আর B দ্বিতীয়টি:
| ফ্ল্যাগ | যে অবস্থায় সেট হয় |
|---|---|
CF |
সবসময় ক্লিয়ার থাকে |
ZF |
A AND B == 0 |
SF |
A AND B-এর সাইন বিট সেট থাকে |
OF |
সবসময় ক্লিয়ার থাকে |
এই ইন্সট্রাকশন দুটি অপারেন্ড নেয় এবং ফ্ল্যাগ আপডেট করে, কিন্তু এর অপারেন্ডগুলো পরিবর্তন করে না।
এই ইন্সট্রাকশনগুলো ডেস্টিনেশন অপারেন্ডের বিটগুলোকে দ্বিতীয় অপারেন্ডে বলা পজিশন সংখ্যা অনুযায়ী সরিয়ে দেয়।
দ্বিতীয় অপারেন্ডটি অবশ্যই একটি ধ্রুব সংখ্যা (একটি immediate) অথবা cl রেজিস্টার হতে হবে (rcx-এর সর্বনিম্ন 8 বিট)।
| নাম | বিবরণ |
|---|---|
shl/sal
|
বিট বামে শিফট করে |
shr/sar
|
বিট ডানে শিফট করে |
লক্ষ্য করুন, দ্বিতীয় অপারেন্ডের কাউন্টটি 5 বিটে মাস্ক করা হয়, অথবা 64-বিট ডেস্টিনেশন অপারেন্ডের ক্ষেত্রে 6 বিটে।
এর পরের যেকোনো বিট কার্যত উপেক্ষিত হয়।
এর মানে হলো সর্বোচ্চ শিফট 31, অথবা 64-বিট অপারেন্ডের ক্ষেত্রে 63।
shl আর sal দুটোই হুবহু একই অপারেশন করে, একটি হলো অন্যটির অ্যালিয়াস।
যখনই বামে শিফট করা হয়, সিকোয়েন্সের শেষ প্রান্ত থেকে শিফটের দৈর্ঘ্যের মধ্যে থাকা বিটগুলোকে প্রথমে CF-এ নেওয়া হয় এবং তারপর বাদ দেওয়া হয়।
অন্যদিকে, শিফটের দৈর্ঘ্যের সমান সংখ্যক নতুন ক্লিয়ার করা বিট শুরুতে যোগ করা হয়।
যেহেতু ইন্টিজারের প্রতিটি বিট 2-এর একটি ঘাত প্রকাশ করে, তাই n পজিশন বামে শিফট করলে ইন্টিজারটিকে 2ⁿ দিয়ে গুণ করার মতো ফল পাওয়া যায়।
ডানে বিট সরানোর জন্য দুটি ইন্সট্রাকশন আছে: shr আর sar।
এই দুটি ইন্সট্রাকশনের যেকোনোটি ব্যবহার করলে, সিকোয়েন্সের শুরু থেকে শিফটের দৈর্ঘ্যের মধ্যে থাকা বিটগুলোকে প্রথমে CF-এ নেওয়া হয় এবং তারপর বাদ দেওয়া হয়।
অন্যদিকে, শিফটের দৈর্ঘ্যের সমান সংখ্যক নতুন বিট শেষে যোগ করা হয়।
এদের মধ্যে পার্থক্য হলো, shr বাম প্রান্তে 0 বিট বসায়, আর sar সবচেয়ে গুরুত্বপূর্ণ বিট সেট থাকলে 1 এবং অন্যথায় 0 বসায়।
এর মানে হলো, sar সাইনড ইন্টিজারের শিফটে সাইন সংরক্ষণ করে।
যেহেতু ইন্টিজারের প্রতিটি বিট 2-এর একটি ঘাত প্রকাশ করে, তাই shr দিয়ে n পজিশন ডানে শিফট করলে 2ⁿ দিয়ে একটি আনসাইনড ভাগ করার মতো ফল পাওয়া যায়।
একইভাবে, sar দিয়ে n পজিশন ডানে শিফট করলে 2ⁿ দিয়ে একটি সাইনড ভাগ করার মতো ফল পাওয়া যায়।
এই ইন্সট্রাকশনগুলো ডেস্টিনেশন অপারেন্ডের বিটগুলোকে দ্বিতীয় অপারেন্ডে বলা পজিশন সংখ্যা অনুযায়ী সরিয়ে দেয়।
দ্বিতীয় অপারেন্ডটি অবশ্যই একটি ধ্রুব সংখ্যা (একটি immediate) অথবা cl রেজিস্টার হতে হবে (rcx-এর সর্বনিম্ন 8 বিট)।
রোটেশন আর শিফটের পার্থক্য হলো, রোটেশন কোনো বিট বাদ দেয় না বা যোগ করে না। শিফটে যে বিটগুলো বাদ পড়ত, সেগুলো বরং উল্টো প্রান্তে সরিয়ে নেওয়া হয়। ফলে সব বিটই টিকে থাকে, শুধু তারা সবাই জায়গা বদলায়।
| নাম | বিবরণ |
|---|---|
rol |
বিট বামে রোটেট করে |
ror |
বিট ডানে রোটেট করে |
লক্ষ্য করুন, দ্বিতীয় অপারেন্ডের কাউন্টটি 5 বিটে মাস্ক করা হয়, অথবা 64-বিট ডেস্টিনেশন অপারেন্ডের ক্ষেত্রে 6 বিটে।
এর পরের যেকোনো বিট কার্যত উপেক্ষিত হয়।
এর মানে হলো সর্বোচ্চ রোটেশন 31, অথবা 64-বিট অপারেন্ডের ক্ষেত্রে 63।
আরও কিছু কাজের বিট ম্যানিপুলেশন ইন্সট্রাকশন আছে:
| নাম | বিবরণ |
|---|---|
popcnt |
সেট করা বিটের সংখ্যা গোনে |
bsr |
সর্বোচ্চ গুরুত্বপূর্ণ সেট বিটের ইনডেক্স দেয়। কোনো বিট সেট না থাকলে ফলাফল অনির্ধারিত |
bsf |
সর্বনিম্ন গুরুত্বপূর্ণ সেট বিটের ইনডেক্স দেয়। কোনো বিট সেট না থাকলে ফলাফল অনির্ধারিত |
এই ইন্সট্রাকশনগুলো সবই দুটি 16-বিট, 32-বিট বা 64-বিট অপারেন্ড নিয়ে কাজ করে।
এগুলো 8-বিট অপারেন্ডের সাথে ব্যবহার করা যায় না।
আপনার বন্ধু এইমাত্র একটি গুরুত্বপূর্ণ গোপন তথ্যসহ আপনাকে একটি মেসেজ পাঠিয়েছে। অন্যরা যাতে সহজে এটি পড়তে না পারে, তাই একের পর এক বিট ম্যানিপুলেশন করে মেসেজটি এনক্রিপ্ট করা হয়েছে। মেসেজটি ডিক্রিপ্ট করতে সাহায্য করার জন্য আপনাকে মেথডগুলো লিখতে হবে।
এই কনসেপ্টে উল্লেখ করা একক-বিট ইনস্ট্রাকশনগুলো হলো:
| নাম | বিবরণ |
|---|---|
| bt | কোনো অপারেন্ড পরিবর্তন না করে বিটটি CF-এ কপি করে |
| bts | বিটটি CF-এ কপি করে এবং ডেস্টিনেশন অপারেন্ডে সেট করে |
| btr | বিটটি CF-এ কপি করে এবং ডেস্টিনেশন অপারেন্ডে ক্লিয়ার করে |
| btc | বিটটি CF-এ কপি করে এবং ডেস্টিনেশন অপারেন্ডে তা কমপ্লিমেন্ট (ফ্লিপ) করে |
এই কনসেপ্টে উল্লেখ করা বিটওয়াইজ ইনস্ট্রাকশনগুলো হলো:
| নাম | বিবরণ |
|---|---|
| and | দুটি বিটই 1 হলে 1 |
| or | অন্তত একটি বিট 1 হলে 1 |
| xor | বিট দুটি আলাদা হলে 1 |
| not | বিট 0 হলে 1; বিট 1 হলে 0 |
এই কনসেপ্টে উল্লেখ করা শিফট ইনস্ট্রাকশনগুলো হলো:
| নাম | বিবরণ |
|---|---|
| shl/sal | বিটগুলো বামে শিফট করে |
| shr/sar | বিটগুলো ডানে শিফট করে |
এই কনসেপ্টে উল্লেখ করা রোটেশন ইনস্ট্রাকশনগুলো হলো:
| নাম | বিবরণ |
|---|---|
| rol | বিটগুলো বামে রোটেট করে |
| ror | বিটগুলো ডানে রোটেট করে |
এই কনসেপ্টে উল্লেখ করা বিবিধ ইনস্ট্রাকশনগুলো হলো:
| নাম | বিবরণ |
|---|---|
| popcnt | সেট করা বিটের সংখ্যা গণনা করে |
| bsr | সবচেয়ে গুরুত্বপূর্ণ সেট বিটের ইনডেক্স দেয়। কোনো বিট সেট না থাকলে ফলাফল অনির্ধারিত |
| bsf | সবচেয়ে কম গুরুত্বপূর্ণ সেট বিটের ইনডেক্স দেয়। কোনো বিট সেট না থাকলে ফলাফল অনির্ধারিত |
মেসেজটি একটি ১৬-বিট ইন্টিজারে এনকোড করা আছে। তবে এর মধ্যে উপরের ৮টি বিট আসলে মেসেজের অংশ নয়, বরং একটি মাস্ক, যা ডিক্রিপশনে ব্যবহার করতে হবে।
extract_higher_bits ফাংশনটি ইমপ্লিমেন্ট করুন, যা একটি ১৬-বিট ইন্টিজার নেয় এবং তার উপরের ৮টি বিট রিটার্ন করে।
extract_higher_bits(0b1010010011000101)
// => 0b10100100
শুধু মাস্ক বের করতে পারলেই হবে না, আপনাকে মেসেজটিও আলাদা করতে হবে।
extract_lower_bits ফাংশনটি ইমপ্লিমেন্ট করুন, যা একটি ১৬-বিট ইন্টিজার নেয় এবং তার নিচের ৮টি বিট রিটার্ন করে।
extract_lower_bits(0b1010010011000101);
// => 0b11000101
কিছু বিট মেসেজ ও মাস্ক দুটোতেই সেট থাকে। এটি অত্যন্ত গুরুত্বপূর্ণ তথ্য, যা পরে কাজে লাগবে।
extract_redundant_bits ইমপ্লিমেন্ট করুন, যা মেসেজ ও মাস্ক দুটোই এনকোড করা একটি ১৬-বিট ইন্টিজার নেয় এবং শুধু রিডান্ড্যান্ট বিটগুলো সেট করা একটি ৮-বিট ইন্টিজার রিটার্ন করে।
রিটার্ন করা সংখ্যায় কোনো বিট মেসেজ ও মাস্ক দুটোতেই 1 থাকলে সেটিও 1 সেট করতে হবে।
বাকি সব বিট ক্লিয়ার করতে হবে।
extract_redundant_bits(0b1010010011000101);
// => 0b10000100
এরপর, মাস্ক অনুযায়ী মেসেজে কিছু বিট 1-এ সেট করতে হবে।
set_message_bits ফাংশনটি ইমপ্লিমেন্ট করুন, যা মেসেজ ও মাস্ক দুটোই এনকোড করা একটি ১৬-বিট ইন্টিজার নেয় এবং মেসেজের বিটগুলো 1-এ সেট করার ফলাফল রিটার্ন করে।
মাস্কের কোনো বিট 1 হলে মেসেজের সেই বিটও 1 সেট করতে হবে।
বাকি সব বিট অপরিবর্তিত রাখতে হবে, যাতে সেগুলো আগে সেট থাকলে সেটই থাকে, আর আগে ক্লিয়ার থাকলে ক্লিয়ারই থাকে।
set_message_bits(0b1010010011000101);
// => 0b11100101
ধাঁধার একটি অংশ মেসেজে সরাসরি নেই: ১৬-বিট সংখ্যা 0b1011001100111100।
এই সংখ্যাটিই আপনার শেয়ার করা প্রাইভেট কী, এবং মেসেজ ডিক্রিপ্ট করতে এটি ব্যবহার করতে হবে।
সেটির জন্য প্রথমে আপনাকে নির্দিষ্ট সংখ্যক পজিশন বামে আপনার প্রাইভেট কী-এর বিটগুলো রোটেট করতে হবে। পজিশনের সংখ্যা হবে মেসেজ ও মাস্ক দুটোতেই সেট থাকা রিডান্ড্যান্ট বিটের সংখ্যার সমান।
rotate_private_key ফাংশনটি ইমপ্লিমেন্ট করুন, যা মেসেজ ও মাস্ক দুটোই এনকোড করা একটি ১৬-বিট ইন্টিজার নেয় এবং আপনার প্রাইভেট কী রোটেট করার ফলাফল রিটার্ন করে।
এই ফলাফলটি একটি ১৬-বিট ইন্টিজার।
rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
NASM (দ্য নেটওয়াইড অ্যাসেম্বলার, এই ট্র্যাক যে অ্যাসেম্বলার ব্যবহার করে) 0b প্রিফিক্স দিয়ে বাইনারি ফরম্যাটে কনস্ট্যান্ট লেখার সাপোর্ট রাখে।
এছাড়া পড়তে সুবিধার জন্য কনস্ট্যান্টে সেপারেটর হিসেবে আন্ডারস্কোর (_) ব্যবহারেরও সাপোর্ট আছে:
PRIVATE_KEY equ 0b1011_0011_0011_1100
ডিক্রিপশনে ব্যবহার করার জন্য প্রাসঙ্গিক বিটগুলো আলাদা করতে আপনার প্রাইভেট কী ফরম্যাট করতে হবে।
একটি প্রাইভেট কী সম্পূর্ণভাবে ফরম্যাট করতে আপনাকে করতে হবে:
ফ্লিপ করা বিট আগে 0 থাকলে 1 হয়, আর আগে 1 থাকলে 0 হয়।
format_private_key ফাংশনটি ইমপ্লিমেন্ট করুন, যা মেসেজ ও মাস্ক দুটোই এনকোড করা একটি ১৬-বিট ইন্টিজার নেয় এবং সম্পূর্ণ ফরম্যাট করা একটি ৮-বিট প্রাইভেট কী রিটার্ন করে।
format_private_key(0b1010010011000101);
// => 0b11000001
সব প্রাসঙ্গিক বিট সেট করা মেসেজ আর ফরম্যাট করা প্রাইভেট কী হাতে এলে, সেগুলো একসাথে জোড়া দিয়ে চূড়ান্ত মেসেজ পাওয়ার সময় এসেছে।
চূড়ান্ত মেসেজটি একটি ১৬-বিট ইন্টিজার, যার মধ্যে:
decrypt_message ফাংশনটি ইমপ্লিমেন্ট করুন, যা মেসেজ ও মাস্ক দুটোই এনকোড করা একটি ১৬-বিট ইন্টিজার নেয় এবং সম্পূর্ণ ডিক্রিপ্ট করা মেসেজসহ একটি ১৬-বিট ইন্টিজার রিটার্ন করে।
এই ফাংশনে আপনার format_private_key দিয়ে তৈরি করা ফরম্যাট করা প্রাইভেট কী এবং set_message_bits দিয়ে সব প্রাসঙ্গিক বিট সেট করা মেসেজ ব্যবহার করা উচিত।
decrypt_message(0b1010010011000101);
// => 0b1100000111100101
Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।