3 पॉइंट द्वारा GN⁺ 2025-07-14 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • x86-64 असेंबली की शुरुआती पढ़ाई के लिए यह श्रृंखला की पहली पोस्ट है
  • आधुनिक 64-बिट सिस्टम के आधार पर टूल इंस्टॉलेशन और बुनियादी संरचना की व्याख्या दी गई है
  • Flat Assembler (FASM) और WinDbg को मुख्य development और debugging टूल के रूप में उपयोग करने का मार्गदर्शन दिया गया है
  • PE format, DLL import, Windows calling convention जैसी व्यावहारिक काम में ज़रूरी मुख्य जानकारियों का सार शामिल है
  • एक साधारण exit program लिखने और debugging प्रक्रिया का अभ्यास करने के अनुभव-केंद्रित विवरण दिए गए हैं

परिचय और महत्व

  • x86 असेंबली को पहली बार सीखते समय विश्वविद्यालयों में अक्सर पुराने वातावरण (16-बिट, DOS, segmented memory) पर आधारित तरीकों से पढ़ाया जाता था
  • आज 64-बिट प्रोसेसर मुख्यधारा में हैं, इसलिए यह श्रृंखला केवल वास्तव में उपयोग होने वाले x86-64 वातावरण पर केंद्रित है और पुराने तत्वों को पूरी तरह बाहर रखती है
  • यह tutorial Windows operating system वातावरण में चलने वाले 64-बिट प्रोग्राम विकसित करने पर केंद्रित है
  • शुरुआत libraries का उपयोग किए बिना, OS तक सीधे पहुँचने वाले न्यूनतम code से की जाती है
  • यह लेख उन developers के लिए है जो पहली बार असेंबली सीखना चाहते हैं, और यह मानकर चलता है कि उन्हें बुनियादी C/C++ ज्ञान है

विकास टूल तैयार करना

Assembler

  • CPU केवल machine code को समझ सकता है, जिसे इंसानों के पढ़ने योग्य रूप में लिखा जाए तो उसे assembly language कहा जाता है
  • assembly language को machine code में बदलने वाला program assembler कहलाता है
  • x86-64 assembly language का कोई एक तय standard नहीं है, और अलग-अलग assemblers का syntax और काम करने का तरीका अलग होता है
  • इस श्रृंखला में Flat Assembler (FASM) का उपयोग किया गया है, जो छोटा है, उपयोग में आसान है, और शक्तिशाली macro system तथा editor प्रदान करता है

Debugger

  • लिखे गए assembly code का विश्लेषण करने और execution flow देखने के लिए debugger एक अनिवार्य टूल है
  • WinDbg की सिफारिश की गई है, क्योंकि इसमें registers, memory और assembly code को अलग-अलग देखा और बदला जा सकता है
  • इसे Windows 10 SDK में केवल ज़रूरी component चुनकर install किया जा सकता है
  • debugger के माध्यम से program की आंतरिक स्थिति, memory structure और register में होने वाले बदलाव सीधे देखे जा सकते हैं

असेंबली प्रोग्रामिंग का दृष्टिकोण

CPU संरचना और instruction set

  • CPU केवल एक निश्चित instruction set के अनुसार सीमित प्रकार के काम कर सकता है
  • Instruction वह बुनियादी कार्य-इकाई है जिसे CPU चला सकता है
  • हर instruction अपने parameters के साथ बहुत सरल काम करता है, जैसे मान सहेजना या arithmetic operation करना
  • low-level programming और debugging में यह समझना महत्वपूर्ण है कि यही संरचना सभी high-level concepts की नींव है

Registers

  • Register CPU के भीतर बना हुआ बहुत तेज़ dedicated memory क्षेत्र होता है
  • x86-64 में 16 general-purpose registers होते हैं और सभी 64-बिट आकार के होते हैं
  • हर register को byte, word और doubleword स्तर पर आंशिक रूप से access किया जा सकता है
रजिस्टर निचला byte निचला word निचला doubleword
rax al ax eax
rbx bl bx ebx
rcx cl cx ecx
rdx dl dx edx
rsp spl sp esp
rsi sil si esi
rdi dil di edi
rbp bpl bp ebp
r8~r15 r8b~r15b r8w~r15w r8d~r15d
  • rsp stack pointer है, rsi/rdi string processing index के रूप में काम करते हैं; इस तरह कुछ registers को विशेष उद्देश्य दिए गए हैं
  • rip instruction pointer है, और rflags एक विशेष register है जो operation results के status flags रखता है

Memory और addresses

  • memory, index 0 से शुरू होने वाले लगातार bytes के array की तरह काम करती है
  • पुराने x86 architecture में segment-offset तरीका आवश्यक था, लेकिन x86-64 में पूरी memory को flat address space के रूप में संभाला जाता है
  • वास्तव में operating system और hardware हर process के लिए virtual address space को physical memory पर dynamically map करते हैं
  • यानी एक ही virtual address अलग-अलग processes में अलग physical memory से जुड़ सकता है
  • instructions और data एक ही memory में मौजूद होते हैं (von Neumann architecture); यह Arduino में उपयोग होने वाले AVR जैसे Harvard architecture से अलग है, जहाँ data अलग रखा जाता है

पहला assembly program लिखना

  • FASM install करने के बाद नीचे दिए गए सरल program code को लिखने और build करने का अभ्यास किया जाता है
format PE64 NX GUI 6.0
entry start

section '.text' code readable executable
start:
        int3
        ret

Code की व्याख्या

  • format PE64 NX GUI 6.0 : FASM द्वारा बनाए जाने वाले executable file format को निर्दिष्ट करता है; यहाँ यह PE (Portable Executable) 64-बिट GUI है
  • entry start : program के entry point को परिभाषित करता है; execution इस label (start) से शुरू होती है
  • section '.text' code readable executable : यह PE का code section है; यह executable क्षेत्र है
  • start: : पहले तय किए गए entry point को नाम देता है
  • int3 : debugger के लिए breakpoint; program को अस्थायी रूप से रोककर उसकी स्थिति जाँचने के लिए उपयोग होता है
  • ret : stack से address निकालकर control को उस स्थान पर भेजने वाला instruction; इस program में यह तुरंत exit प्रतिक्रिया देता है

Debugging अभ्यास

  • WinDbg में ऊपर वाले program की executable file (.exe) खोलकर disassembly, registers आदि की विभिन्न windows तैयार की जाती हैं

  • F5 दबाने पर program breakpoint तक पहुँचता है, और F8 दबाने पर हर बार एक-एक instruction execute होता है

  • registers (rip आदि) में होने वाले बदलाव real time में देखे जा सकते हैं

  • ret चलने के बाद control operating system को चला जाता है, और फिर RtlExitUserThread को call करते हुए thread और process का exit आगे बढ़ता है

  • ध्यान दें: केवल ret instruction से exit करने पर, thread के अलावा अतिरिक्त background execution होने की स्थिति में process बचा रह सकता है; इसलिए सही तरीके से exit करने के लिए ExitProcess को call करना बेहतर है

PE format और DLL import

DLL function import संरचना का अवलोकन

  • ExitProcess जैसी WinAPI functions KERNEL32.DLL में होती हैं
  • ऐसी external functions का उपयोग करने के लिए executable file की import table (.idata section) बनानी पड़ती है
  • idata section की Import Directory Table (IDT) में DLL name, function name, IAT/ILT आदि के address (RVA) की जानकारी होती है
  • IAT (Import Address Table) को runtime पर OS loader वास्तविक function addresses से overwrite करता है
  • Hint/Name Table में हर function का नाम और hint information होती है

FASM में .idata section परिभाषित करने का उदाहरण

section '.idata' import readable writeable
idt: 
    dd rva kernel32_iat
    dd 0
    dd 0
    dd rva kernel32_name
    dd rva kernel32_iat
    dd 5 dup(0)
name_table: 
    _ExitProcess_Name dw 0
                      db "ExitProcess", 0, 0
kernel32_name: db "KERNEL32.DLL", 0
kernel32_iat: 
    ExitProcess dq rva _ExitProcess_Name
    dq 0 
  • db/dw/dd/dq : byte/word/doubleword/quadword (8-byte) इकाइयों में values insert करते हैं
  • rva : symbol का virtual address (Relative Virtual Address) calculate करता है
  • IAT और Name Table को manually बनाकर DLL function references का उपयोग किया जा सकता है

64-बिट Windows calling convention (MS x64 Calling Convention)

  • function call के समय arguments pass करने और stack उपयोग का तरीका तय करने वाला standard convention
  • 64-बिट Windows में Microsoft x64 Calling Convention का उपयोग होता है
  • मुख्य विशेषताएँ:
    • stack pointer हमेशा 16-byte aligned होना चाहिए
    • पहले 4 integer/pointer arguments rcx, rdx, r8, r9 registers में दिए जाते हैं
    • पहले 4 floating-point arguments xmm0~xmm3 में रखे जाते हैं
    • अतिरिक्त arguments के लिए stack का उपयोग होता है
    • arguments की संख्या से स्वतंत्र होकर stack पर 32-byte shadow space आरक्षित करना होता है
    • stack cleanup की ज़िम्मेदारी caller की होती है

ExitProcess call का उदाहरण

format PE64 NX GUI 6.0
entry start

section '.text' code readable executable
start:
    int3
    sub rsp, 8 * 5
    xor rcx, rcx
    call [ExitProcess]

section '.idata' import readable writeable
idt: 
    dd rva kernel32_iat
    dd 0
    dd 0
    dd rva kernel32_name
    dd rva kernel32_iat
    dd 5 dup(0)
name_table: 
    _ExitProcess_Name dw 0
                      db "ExitProcess", 0, 0
kernel32_name db "KERNEL32.DLL", 0
kernel32_iat: 
    ExitProcess dq rva _ExitProcess_Name
    dq 0 

नए code का विश्लेषण

  • sub rsp, 8 * 5 : stack pointer को adjust करता है (40 bytes reserve), और एक साथ 16-byte alignment तथा shadow space सुनिश्चित करता है

  • xor rcx, rcx : पहले argument register rcx में 0 सेट करता है (इसे exit code के रूप में उपयोग किया जाता है)

  • call [ExitProcess] : import table में वास्तव में लिखे गए ExitProcess के function address पर jump करता है

  • WinDbg में step-by-step execution के दौरान stack pointer (rsp) और rcx register के बदलाव तथा process exit flow को सीधे देखा जा सकता है

समापन

  • यह लेख बुनियादी tool setup से लेकर PE format, DLL import, x64 calling convention, पहला program लिखना और debugging तक x86-64 असेंबली के समग्र flow को hands-on तरीके से समझाता है
  • अगले भाग में और विविध features के implementation और वास्तविक code पर चर्चा की जाएगी

1 टिप्पणियां

 
GN⁺ 2025-07-14
Hacker News राय
  • मैं कई वर्षों से विकसित कर रहे एक प्रोजेक्ट को साझा करना चाहता हूँ
    https://asm-editor.specy.app
    यह एक ऑनलाइन interactive IDE है जो M68K, MIPS, RISC-V, X86 आदि कई assembly भाषाओं को सपोर्ट करता है
    इसमें assembly programming सिखाने के लिए कई तरह की सुविधाएँ हैं
    इसे दूसरी वेबसाइटों में embed भी किया जा सकता है

  • मुझे नहीं पता था कि pointer indexing registers के low-address byte तक सीधे पहुँचने की सुविधा होती है (उदाहरण: 16/32-bit में si/esi को sil के रूप में access किया जा सकता है)
    यह ax/eax से al को access करने जैसी ही अवधारणा है
    मैं जानना चाहता हूँ कि x86_64 में इसके लिए जो नए opcode जोड़े गए, वे वास्तव में मौजूद हैं या नहीं
    लग रहा है कि platform specification फिर से देखनी पड़ेगी
    यह बस शुद्ध जिज्ञासा से पूछा है

  • मैं अपने द्वारा लिखी गई assembly की शुरुआती सामग्री साझा कर रहा हूँ
    https://www.nayuki.io/page/a-fundamental-introduction-to-x86-assembly-programming

  • मैं यह जानना चाहता था कि क्या मैं अपने CPU emulator dispatch को C++ से तेज बना सकता हूँ, इसलिए assembly में optimize करने की कोशिश की
    मैंने Fibonacci प्रोग्राम चलाकर देखा, लेकिन नतीजा उसके आस-पास भी नहीं पहुँचा
    आखिर में मैंने इसे सिर्फ default-disabled option के रूप में merge किया
    फिर भी मुझे यक़ीन है कि इसे और तेज करने का कोई तरीका ज़रूर होगा
    https://github.com/libriscv/libriscv/blob/master/lib/libriscv/amd64/inaccurate_dispatch.nasm
    memory access का तरीका सीखते हुए performance में थोड़ा सुधार किया
    jump table को 64-bit से 32-bit तक घटाया और उसे .text section में रखा ताकि वह RIP-relative access बन जाए
    Fibonacci प्रोग्राम को ज़्यादा bytecode की ज़रूरत नहीं थी
    मैं सच में और सुधार के लिए सुझाव सुनना चाहूँगा

    • क्या आपने अपने लिखे हुए code की सीधे उस code से तुलना की है जो C++ compiler generate करता है?
      मुझे पूरा context नहीं पता, लेकिन मुझे लगता है कि अंतर dispatch mechanism (instruction fetch का तरीका) की वजह से नहीं, बल्कि वास्तविक instruction implementation के अंतर की वजह से भी हो सकता है
      optimization के तौर पर, emulated registers को x86-64 के वास्तविक registers पर map किया जा सकता है और उन्हें memory में जाने ही न दिया जाए
      ऐसा करने पर add जैसी operations memory से निकाले बिना सीधे की जा सकती हैं
      हालांकि, इस तरीके से emulator लिखना काफ़ी ज़्यादा झंझटभरा हो जाता है
  • यह browser में अभ्यास किया जा सकने वाला x86 assembly का शुरुआती परिचय है
    बिना किसी खास local setup के उदाहरणों को तुरंत चलाया जा सकता है
    https://shikaan.github.io/assembly/x86/guide/2024/09/08/x86-64-introduction-hello.html
    संदर्भ के लिए, यह सामग्री मैंने ही लिखी है

    • क्या इसमें input validation अलग से किया जाता है?
      यह NASM से सीधे assemble करके binary चलाने जैसा लग रहा है, इसलिए security को लेकर जिज्ञासा हुई
  • सिर्फ profile photo देखकर मुझे लगा यह junferno है

  • assembly को बस एक बार भी हाथ लगाकर देखने से समग्र समझ गहरी हो जाती है, इसलिए यह हमेशा अच्छा अनुभव होता है
    कोई बड़ा प्रोजेक्ट बनाना ज़रूरी नहीं है, इसलिए हिम्मत करके थोड़ा-सा ही सही, खुद करके देखने की सलाह दूँगा

  • (2020) उस समय की HN discussion link साझा कर रहा हूँ
    https://news.ycombinator.com/item?id=24195627

  • अच्छा लगा कि यह Intel-style assembly syntax है

    • अब जिज्ञासा हो रही है कि दूसरे assembly syntax कौन-कौन से हैं
  • assembly में कुछ करना तो चाहता हूँ, लेकिन कोई खास idea सूझ नहीं रहा

    • मैं TIS-100 गेम की सिफारिश करूँगा
      यह एक तरह का pseudo-assembly puzzle game है
      मुझे लगता है ऐसे गेम assembly के प्रति आपकी जिज्ञासा को संतुष्ट कर सकते हैं