पूर्णांक के हर बिट का उपयोग एक बाइनरी वैल्यू संग्रहीत करने के लिए किया जा सकता है। चूँकि बहुत सी स्थितियों में बाइनरी जानकारी होती है, जैसे सही या गलत, शामिल होना या न होना, चालू या बंद, इसलिए N-बिट पूर्णांक का बाइनरी निरूपण N चीज़ों की बाइनरी स्थिति को एनकोड करने का एक संक्षिप्त तरीका देता है। इसलिए असेंबली में बिट और बाइट को मैनिपुलेट करने की क्षमता आवश्यक हो जाती है। x86-64 निर्देश सेट बिटवाइज़ मैनिपुलेशन के लिए कई तरह के निर्देश देता है।
ये निर्देश ऑपरेंड के एकल बिट पर काम करते हैं।
इन सभी को दो ऑपरेंड चाहिए, जिनमें दूसरा यह इंडेक्स बताता है कि पहले ऑपरेंड में किस बिट पर काम किया जा रहा है। ये सभी चुने गए बिट को कैरी फ्लैग (CF) में कॉपी करते हैं।
| नाम | विवरण |
|---|---|
bt |
बिट को CF में कॉपी करता है, बिना किसी ऑपरेंड को बदले |
bts |
बिट को CF में कॉपी करता है और उसे डेस्टिनेशन ऑपरेंड में सेट करता है |
btr |
बिट को CF में कॉपी करता है और उसे डेस्टिनेशन ऑपरेंड में क्लियर करता है |
btc |
बिट को CF में कॉपी करता है और उसे डेस्टिनेशन ऑपरेंड में कॉम्प्लिमेंट (फ्लिप) करता है |
बिटवाइज़ ऑपरेशन ऑपरेंड के सभी बिट पर किए जाते हैं।
हर निष्पादित बिटवाइज़ ऑपरेशन के लिए उसी नाम का एक निर्देश होता है:
| नाम | विवरण |
|---|---|
and |
1, अगर दोनों बिट 1 हैं |
or |
1, अगर कम से कम एक बिट 1 है |
xor |
1, अगर बिट अलग-अलग हैं |
not |
1, अगर बिट 0 था; 0, अगर बिट 1 था |
इनमें से अधिकांश दो ऑपरेंड लेते हैं, दोनों पर बिटवाइज़ ऑपरेशन करते हैं और परिणाम को डेस्टिनेशन ऑपरेंड में संग्रहीत करते हैं।
अपवाद not है, जो केवल एक डेस्टिनेशन ऑपरेंड लेता है।
जब हम एक और शून्य को क्रमशः शामिल होना और न होना मानते हैं, तो पूर्णांक को बिटमास्क (या सिर्फ मास्क) कहा जाता है।
एक बिटमास्क चीज़ों को "मास्क आउट" करता है, क्योंकि i-वें बिट में शून्य होने का मतलब है कि i-वाँ आइटम शामिल नहीं है, जबकि एक होने का मतलब है कि वह शामिल है।
हम आम तौर पर बिटमास्क का उपयोग पूर्णांक के कुछ बिट को शामिल करने और बाकी को बाहर रखने के लिए भी करते हैं।
उदाहरण के लिए, मान लीजिए A एक ऐसा पूर्णांक है जिसका बाइनरी निरूपण यह है:
| इंडेक्स | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| बिट | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 |
साथ ही, मान लीजिए M एक ऐसा पूर्णांक है जिसका बाइनरी निरूपण यह है:
| इंडेक्स | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| बिट | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
दोनों 8-बिट पूर्णांक हैं।
इस स्थिति में, हम कह सकते हैं कि M A के बिट 0, 2 और 3 चुनता है, और बाकी को बाहर रखता है।
पहले बताए गए बिटवाइज़ निर्देश मास्क के साथ पूर्णांक को मैनिपुलेट करने में उपयोगी होते हैं। उदाहरण के लिए:
A के जो बिट M ने नहीं चुने हैं, उन्हें क्लियर करने के लिए बिटवाइज़ 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 | 1 अगर बिट 0 था; 0 अगर बिट 1 था |
इस कॉन्सेप्ट में बताए गए शिफ्ट निर्देश ये हैं:
| नाम | विवरण |
|---|---|
| shl/sal | बिट्स को बाईं ओर शिफ्ट करता है |
| shr/sar | बिट्स को दाईं ओर शिफ्ट करता है |
इस कॉन्सेप्ट में बताए गए रोटेशन निर्देश ये हैं:
| नाम | विवरण |
|---|---|
| rol | बिट्स को बाईं ओर घुमाता है |
| ror | बिट्स को दाईं ओर घुमाता है |
इस कॉन्सेप्ट में बताए गए विविध निर्देश ये हैं:
| नाम | विवरण |
|---|---|
| popcnt | सेट बिट्स की संख्या गिनता है |
| bsr | सबसे महत्वपूर्ण सेट बिट का इंडेक्स देता है। अगर कोई बिट सेट नहीं है, तो परिणाम अपरिभाषित होता है |
| bsf | सबसे कम महत्वपूर्ण सेट बिट का इंडेक्स देता है। अगर कोई बिट सेट नहीं है, तो परिणाम अपरिभाषित होता है |
संदेश एक 16-बिट पूर्णांक में एन्कोड किया गया है। लेकिन उसमें से ऊपर के 8 बिट असल में संदेश का हिस्सा नहीं हैं, बल्कि एक मास्क हैं जिसका इस्तेमाल डिक्रिप्शन में करना है।
extract_higher_bits फंक्शन बनाइए जो एक 16-बिट पूर्णांक लेता है और उसके ऊपर के 8 बिट लौटाता है।
extract_higher_bits(0b1010010011000101)
// => 0b10100100
सिर्फ मास्क निकाल पाना काफी नहीं है, आपको संदेश को भी अलग करना होगा।
extract_lower_bits फंक्शन बनाइए जो एक 16-बिट पूर्णांक लेता है और उसके नीचे के 8 बिट लौटाता है।
extract_lower_bits(0b1010010011000101);
// => 0b11000101
कुछ बिट संदेश और मास्क दोनों में सेट होते हैं। यह बहुत ज़रूरी जानकारी है जिसका इस्तेमाल आगे किया जाएगा।
extract_redundant_bits फंक्शन बनाइए जो संदेश और मास्क दोनों को एन्कोड करने वाला 16-बिट पूर्णांक लेता है और एक ऐसा 8-बिट पूर्णांक लौटाता है जिसमें सिर्फ रिडंडेंट बिट सेट हों।
लौटाई गई संख्या में कोई बिट तब 1 होना चाहिए जब वह संदेश और मास्क दोनों में 1 हो।
बाकी सारे बिट क्लियर होने चाहिए।
extract_redundant_bits(0b1010010011000101);
// => 0b10000100
इसके बाद, मास्क के अनुसार संदेश में कुछ बिट को 1 पर सेट करना है।
set_message_bits फंक्शन बनाइए जो संदेश और मास्क दोनों को एन्कोड करने वाला 16-बिट पूर्णांक लेता है और संदेश के बिट को 1 पर सेट करने का परिणाम लौटाता है।
संदेश का कोई बिट तब 1 होना चाहिए जब मास्क में वह बिट 1 हो।
बाकी सारे बिट अपरिवर्तित रखे जाने चाहिए, यानी अगर वे पहले से सेट थे तो सेट रहें, और अगर पहले से क्लियर थे तो क्लियर रहें।
set_message_bits(0b1010010011000101);
// => 0b11100101
पहेली का एक टुकड़ा ऐसा है जो संदेश में साफ़ तौर पर मौजूद नहीं है: 16-बिट संख्या 0b1011001100111100।
यह संख्या आपकी साझा प्राइवेट की है और संदेश को डिक्रिप्ट करने में आपको इसका इस्तेमाल करना है।
ऐसा करने के लिए, सबसे पहले आपको अपनी प्राइवेट की के बिट को कुछ पोज़िशन बाईं ओर घुमाना होगा। पोज़िशन की संख्या उतनी ही है जितने रिडंडेंट बिट संदेश और मास्क दोनों में सेट हैं।
rotate_private_key फंक्शन बनाइए जो संदेश और मास्क दोनों को एन्कोड करने वाला 16-बिट पूर्णांक लेता है और आपकी प्राइवेट की घुमाने का परिणाम लौटाता है।
यह परिणाम एक 16-बिट पूर्णांक होता है।
rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
NASM (The Netwide Assembler, यानी इस ट्रैक में इस्तेमाल होने वाला असेंबलर) में 0b लगाकर बाइनरी फॉर्मैट में कॉन्स्टेंट लिखने की सुविधा है।
यह पढ़ने में आसानी के लिए कॉन्स्टेंट में अंडरस्कोर (_) को सेपरेटर की तरह इस्तेमाल करने की सुविधा भी देता है:
PRIVATE_KEY equ 0b1011_0011_0011_1100
डिक्रिप्शन में इस्तेमाल होने के लिए आपकी प्राइवेट की को फॉर्मैट करना ज़रूरी है ताकि उसके काम के बिट अलग किए जा सकें।
किसी प्राइवेट की को पूरी तरह फॉर्मैट करने के लिए आपको यह करना होगा:
पलटा हुआ बिट 1 होता है अगर वह 0 था, और 0 होता है अगर वह 1 था।
format_private_key फंक्शन बनाइए जो संदेश और मास्क दोनों को एन्कोड करने वाला 16-बिट पूर्णांक लेता है और पूरी तरह फॉर्मैट की गई 8-बिट प्राइवेट की लौटाता है।
format_private_key(0b1010010011000101);
// => 0b11000001
जब आपके पास सारे काम के बिट सेट किया हुआ संदेश और फॉर्मैट की गई प्राइवेट की आ जाए, तो उन्हें जोड़कर अंतिम संदेश निकालने का समय आ जाता है।
अंतिम संदेश एक 16-बिट पूर्णांक होता है, जिसमें:
decrypt_message फंक्शन बनाइए जो संदेश और मास्क दोनों को एन्कोड करने वाला 16-बिट पूर्णांक लेता है और पूरी तरह डिक्रिप्ट किए हुए संदेश वाला 16-बिट पूर्णांक लौटाता है।
इस फंक्शन में आपको format_private_key से बनाई गई फॉर्मैट की हुई प्राइवेट की इस्तेमाल करनी चाहिए, और set_message_bits से सारे काम के बिट सेट किया हुआ संदेश भी।
decrypt_message(0b1010010011000101);
// => 0b1100000111100101
Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।