Intel x86 Assembly Language & Microarchitecture ट्यूटोरियल
इंटेल x86 असेंबली लैंग्वेज और माइक्रोआर्किटेक्चर के साथ शुरुआत करना
खोज…
टिप्पणियों
यह खंड x86 क्या है का एक सिंहावलोकन प्रदान करता है, और क्यों एक डेवलपर इसका उपयोग करना चाहता है।
इसमें x86 के भीतर किसी भी बड़े विषयों का उल्लेख होना चाहिए, और संबंधित विषयों के लिए लिंक करना चाहिए। चूंकि x86 के लिए दस्तावेज़ीकरण नया है, इसलिए आपको उन संबंधित विषयों के प्रारंभिक संस्करण बनाने की आवश्यकता हो सकती है।
x86 विधानसभा भाषा
X86 असेंबली भाषाओं का परिवार मूल इंटेल 8086 आर्किटेक्चर पर दशकों के विकास का प्रतिनिधित्व करता है। असेंबलर के उपयोग के आधार पर कई अलग-अलग बोलियाँ होने के अलावा, अतिरिक्त प्रोसेसर निर्देश, रजिस्टरों और अन्य विशेषताओं को पिछले कुछ वर्षों में जोड़ा गया है, जबकि अभी भी 1980 के दशक में इस्तेमाल किए गए 16-बिट असेंबली के लिए संगत है।
X86 असेंबली के साथ काम करने का पहला चरण यह निर्धारित करना है कि लक्ष्य क्या है। यदि आप एक ऑपरेटिंग सिस्टम के भीतर कोड लिखना चाहते हैं, उदाहरण के लिए, आप अतिरिक्त रूप से यह निर्धारित करना चाहेंगे कि क्या आप किसी उच्च स्तरीय भाषा की स्टैंड-अलोन असेंबलर या बिल्ट-इन-इन असेंबली सुविधाओं का उपयोग करना पसंद करेंगे, जैसे कि यदि आप सी। एक ऑपरेटिंग सिस्टम के बिना "नंगे धातु" पर नीचे कोड करने की इच्छा, आपको बस अपनी पसंद के कोडांतरक को स्थापित करने और समझने की आवश्यकता है कि बाइनरी कोड कैसे बनाया जाए जिसे फ्लैश मेमोरी, बूट करने योग्य छवि में बदल दिया जाए या अन्यथा मेमोरी में लोड किया जाए। निष्पादन शुरू करने के लिए उपयुक्त स्थान।
एक बहुत ही लोकप्रिय असेंबलर जो कई प्लेटफार्मों पर अच्छी तरह से समर्थित है, NASM (नेटवाइड असेंबलर) है, जिसे http://nasm.us/ से प्राप्त किया जा सकता है। NASM साइट पर आप अपने प्लेटफ़ॉर्म के लिए नवीनतम रिलीज़ बिल्ड डाउनलोड करने के लिए आगे बढ़ सकते हैं।
खिड़कियाँ
एनएएसएम के 32-बिट और 64-बिट संस्करण दोनों विंडोज के लिए उपलब्ध हैं। एनएएसएम एक सुविधाजनक इंस्टॉलर के साथ आता है जिसका उपयोग आपके विंडोज होस्ट पर कोडांतरक को स्वचालित रूप से स्थापित करने के लिए किया जा सकता है।
लिनक्स
यह अच्छी तरह से हो सकता है कि NASM आपके लिनक्स के संस्करण पर पहले से ही स्थापित है। जांच करने के लिए, निष्पादित करें:
nasm -v
यदि कमांड नहीं मिली है, तो आपको इंस्टॉल करने की आवश्यकता होगी। जब तक आप ऐसा कुछ नहीं कर रहे हैं जिसके लिए ब्लीडिंग एज NASM फीचर्स की आवश्यकता है, तो सबसे अच्छा तरीका है कि आप NASM को स्थापित करने के लिए अपने लिनक्स वितरण के लिए बिल्ट-इन पैकेज मैनेजमेंट टूल का उपयोग करें। उदाहरण के लिए, डेबियन-व्युत्पन्न सिस्टम जैसे उबंटू और अन्य के तहत, कमांड प्रॉम्प्ट से निम्नलिखित निष्पादित करें:
sudo apt-get install nasm
RPM आधारित प्रणालियों के लिए, आप कोशिश कर सकते हैं:
sudo yum install nasm
मैक ओएस एक्स
ओएस एक्स के हाल के संस्करण (योसमाइट और एल कैपिटान सहित) एनएएसएम के पुराने संस्करण के साथ आते हैं जो पहले से स्थापित है। उदाहरण के लिए, एल कैपिटन का संस्करण 0.98.40 है। हालांकि यह लगभग सभी सामान्य उद्देश्यों के लिए काम करेगा, यह वास्तव में काफी पुराना है। इस लेखन में, NASM संस्करण २.११ जारी किया गया है और २.१२ में कई रिलीज़ उम्मीदवार उपलब्ध हैं।
आप उपरोक्त लिंक से एनएएसएम स्रोत कोड प्राप्त कर सकते हैं, लेकिन जब तक आपको स्रोत से स्थापित करने की कोई विशिष्ट आवश्यकता नहीं है, तब तक ओएस एक्स रिलीज़ निर्देशिका से बाइनरी पैकेज डाउनलोड करना और इसे अनज़िप करना बहुत सरल है।
एक बार अनज़िप करने के बाद, यह दृढ़ता से अनुशंसा की जाती है कि आप NASM के सिस्टम-इंस्टॉल किए गए संस्करण को ओवरराइट न करें । इसके बजाय, आप इसे / usr / स्थानीय में स्थापित कर सकते हैं:
$ sudo su
<user's password entered to become root>
# cd /usr/local/bin
# cp <path/to/unzipped/nasm/files/nasm> ./
# exit
इस बिंदु पर, NASM /usr/local/bin , लेकिन यह आपके पथ में नहीं है। अब आपको अपनी प्रोफ़ाइल के अंत में निम्नलिखित पंक्ति जोड़नी चाहिए:
$ echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bash_profile
यह आपके रास्ते के लिए /usr/local/bin को प्रीपेंड करेगा। कमांड प्रॉम्प्ट पर nasm -v अब उचित, नया, संस्करण प्रदर्शित करना चाहिए।
x86 लिनक्स हैलो वर्ल्ड उदाहरण
यह 32-बिट x86 लिनक्स के लिए NASM असेंबली में एक बुनियादी हैलो वर्ल्ड प्रोग्राम है, सीधे सिस्टम कॉल (बिना किसी libc फ़ंक्शन कॉल) का उपयोग किए। इसमें बहुत कुछ लेना है, लेकिन समय के साथ यह समझ में आने लगेगा। अर्धविराम ( ; ) से शुरू होने वाली पंक्तियाँ टिप्पणियाँ हैं।
यदि आप पहले से ही निम्न-स्तरीय यूनिक्स सिस्टम प्रोग्रामिंग को नहीं जानते हैं, तो आप केवल एएसएम में फ़ंक्शन लिखना और उन्हें C या C ++ प्रोग्राम से कॉल कर सकते हैं। फिर आप बस यह जानने के बारे में चिंता कर सकते हैं कि रजिस्टर और मेमोरी को कैसे संभालना है, बिना पोसिक्स सिस्टम-कॉल एपीआई और एबीआई का उपयोग किए बिना भी सीखना।
यह दो सिस्टम कॉल करता है: write(2) और _exit(2) ( exit(3) लिबास रैपर जो स्टीडियो बफर को फ्लश करता है और इसी तरह)। (तकनीकी रूप से, _exit() sys_exit_group कॉल करता है, sys_exit नहीं, लेकिन यह केवल एक बहु-थ्रेडेड प्रक्रिया में मायने रखता है ।) सामान्य रूप से सिस्टम कॉल के बारे में दस्तावेज़ीकरण के लिए syscalls(2) भी देखें, और उन्हें सीधे उपयोग कर बनाम libc का उपयोग करने के बीच अंतर। आवरण कार्य।
सारांश में, सिस्टम कॉल उपयुक्त रजिस्टरों में आर्गन्स रखकर किए जाते हैं, और सिस्टम कॉल नंबर eax , फिर एक int 0x80 निर्देश चल रहा है। यह भी देखें कि विधानसभा में सिस्टम कॉल्स के रिटर्न वैल्यू क्या हैं? ज्यादातर सी सिंटैक्स के साथ asm syscall इंटरफ़ेस कैसे प्रलेखित है, इसके अधिक विवरण के लिए।
32-बिट ABI के लिए syscall कॉल नंबर में हैं /usr/include/i386-linux-gnu/asm/unistd_32.h (में एक ही सामग्री /usr/include/x86_64-linux-gnu/asm/unistd_32.h )।
#include <sys/syscall.h> अंततः सही फ़ाइल शामिल होगी, इसलिए आप echo '#include <sys/syscall.h>' | gcc -E - -dM | less मैक्रो डिफ देखने के लिए echo '#include <sys/syscall.h>' | gcc -E - -dM | less ( सी हेडर में asm के लिए स्थिरांक खोजने के बारे में अधिक के लिए यह उत्तर देखें)
section .text ; Executable code goes in the .text section
global _start ; The linker looks for this symbol to set the process entry point, so execution start here
;;;a name followed by a colon defines a symbol. The global _start directive modifies it so it's a global symbol, not just one that we can CALL or JMP to from inside the asm.
;;; note that _start isn't really a "function". You can't return from it, and the kernel passes argc, argv, and env differently than main() would expect.
_start:
;;; write(1, msg, len);
; Start by moving the arguments into registers, where the kernel will look for them
mov edx,len ; 3rd arg goes in edx: buffer length
mov ecx,msg ; 2nd arg goes in ecx: pointer to the buffer
;Set output to stdout (goes to your terminal, or wherever you redirect or pipe)
mov ebx,1 ; 1st arg goes in ebx: Unix file descriptor. 1 = stdout, which is normally connected to the terminal.
mov eax,4 ; system call number (from SYS_write / __NR_write from unistd_32.h).
int 0x80 ; generate an interrupt, activating the kernel's system-call handling code. 64-bit code uses a different instruction, different registers, and different call numbers.
;; eax = return value, all other registers unchanged.
;;;Second, exit the process. There's nothing to return to, so we can't use a ret instruction (like we could if this was main() or any function with a caller)
;;; If we don't exit, execution continues into whatever bytes are next in the memory page,
;;; typically leading to a segmentation fault because the padding 00 00 decodes to add [eax],al.
;;; _exit(0);
xor ebx,ebx ; first arg = exit status = 0. (will be truncated to 8 bits). Zeroing registers is a special case on x86, and mov ebx,0 would be less efficient.
;; leaving out the zeroing of ebx would mean we exit(1), i.e. with an error status, since ebx still holds 1 from earlier.
mov eax,1 ; put __NR_exit into eax
int 0x80 ;Execute the Linux function
section .rodata ; Section for read-only constants
;; msg is a label, and in this context doesn't need to be msg:. It could be on a separate line.
;; db = Data Bytes: assemble some literal bytes into the output file.
msg db 'Hello, world!',0xa ; ASCII string constant plus a newline (0x10)
;; No terminating zero byte is needed, because we're using write(), which takes a buffer + length instead of an implicit-length string.
;; To make this a C string that we could pass to puts or strlen, we'd need a terminating 0 byte. (e.g. "...", 0x10, 0)
len equ $ - msg ; Define an assemble-time constant (not stored by itself in the output file, but will appear as an immediate operand in insns that use it)
; Calculate len = string length. subtract the address of the start
; of the string from the current position ($)
;; equivalently, we could have put a str_end: label after the string and done len equ str_end - str
लिनक्स पर, आप इस फाइल को Hello.asm रूप में सहेज सकते हैं और इन कमांड के साथ इसमें से 32-बिट निष्पादन योग्य बना सकते हैं:
nasm -felf32 Hello.asm # assemble as 32-bit code. Add -Worphan-labels -g -Fdwarf for debug symbols and warnings
gcc -nostdlib -m32 Hello.o -o Hello # link without CRT startup code or libc, making a static binary
NASM / YASM सिंटैक्स या GNU AT & T सिंटैक्स के निर्देश के as GNM के साथ 32 या 64-बिट स्टैटिक या डायनेमिक रूप से लिंक किए गए Linux निष्पादक में असेंबली बनाने के बारे में अधिक जानकारी के लिए यह उत्तर देखें। (मुख्य बिंदु: 64-बिट होस्ट पर 32-बिट कोड का निर्माण करते समय -m32 या समकक्ष का उपयोग करना सुनिश्चित करें, या आपको रन-टाइम पर भ्रामक समस्याएं होंगी।)
आप इसे सिस्टम द्वारा किए गए कॉल को देखने के लिए strace साथ निष्पादित कर सकते हैं:
$ strace ./Hello
execve("./Hello", ["./Hello"], [/* 72 vars */]) = 0
[ Process PID=4019 runs in 32 bit mode. ]
write(1, "Hello, world!\n", 14Hello, world!
) = 14
_exit(0) = ?
+++ exited with 0 +++
स्टडर पर ट्रेस और स्टडआउट पर नियमित आउटपुट दोनों ही टर्मिनल पर जा रहे हैं, इसलिए वे write सिस्टम कॉल के साथ हस्तक्षेप करते हैं। यदि आप परवाह करते हैं तो फ़ाइल को पुनर्निर्देशित या ट्रेस करें। ध्यान दें कि यह हमें आसानी से syscall रिटर्न मानों को देखने के लिए उन्हें जोड़ने के लिए कोड जोड़ने के बिना देता है, और वास्तव में इसके लिए नियमित डीबगर (जैसे gdb) का उपयोग करने से भी आसान है।
इस कार्यक्रम का x86-64 संस्करण अत्यंत समान होगा, एक ही सिस्टम कॉल में एक ही आर्गन्स को पास करना, बस विभिन्न रजिस्टरों में। और int 0x80 बजाय syscall अनुदेश का उपयोग करना।