Foundationপ্রথম নীতি থেকে
LEVEL 3লেসন ২/১২কঠিন১ ঘণ্টা

x86-64 Syntax — AT&T বনাম Intel, একই instruction দুই ভাষায়

x86-64 Syntax: AT&T vs Intel

একই instruction, দুই সম্পূর্ণ ভিন্ন দেখতে টেক্সট — AT&T syntax (GNU টুলচেইনের ডিফল্ট: gdb, objdump, gas) বনাম Intel syntax (Intel-এর নিজস্ব ডকুমেন্টেশন, NASM/MASM-এর ডিফল্ট)। Operand order উল্টো, register/immediate prefix ভিন্ন, কিন্তু নিচে ঠিক একই বাইট — এই লেসন দুই syntax পাশাপাশি শেখাবে, register set আর মূল instruction category বাস্তব উদাহরণসহ।

এই লেসন শেষে আপনি পারবেন

  • AT&T আর Intel syntax-এর মধ্যে চারটা মূল পার্থক্য (operand order, register prefix, immediate prefix, size suffix) নির্ভুলভাবে বর্ণনা ও প্রয়োগ করতে পারবেন
  • এই বিভাজনের ঐতিহাসিক উৎস ব্যাখ্যা করতে পারবেন — AT&T কেন Unix assembler ঐতিহ্য থেকে এসেছে, Intel syntax কেন Intel-এর নিজস্ব ডকুমেন্টেশন ও NASM/MASM-এর ডিফল্ট
  • gdb ও objdump-কে Intel syntax-এ পরিবর্তন করতে পারবেন (`set disassembly-flavor intel`, `objdump -M intel`), আর বুঝতে পারবেন এটা শুধু টেক্সট-উপস্থাপনার পরিবর্তন, machine code অপরিবর্তিত
  • x86-64-র পূর্ণ general-purpose register set (rax-r15) আর তাদের ৩২/১৬/৮-বিট sub-register view নির্ভুলভাবে তালিকাভুক্ত করতে পারবেন, sub-register লেখার zero-extension নিয়মসহ
  • mov, arithmetic (add/sub/imul/idiv), comparison (cmp/test), আর conditional jump (je/jne/jl/jg) instruction category-র বাস্তব উদাহরণ দুই syntax-এই লিখতে ও পড়তে পারবেন
  • একটা compiled function-এর assembly output AT&T থেকে Intel-এ (বা উল্টো) হাতে অনুবাদ করতে পারবেন, ভুল operand-order এড়িয়ে

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

গত লেসনের শেষে একটা পর্যবেক্ষণ রেখে এসেছিলাম। square.c কম্পাইল করে যে assembly পেয়েছিলাম, সেটা দেখতে ছিল এমন:

movl    %edi, -4(%rbp)
imull   -4(%rbp), %eax

আর প্রতিশ্রুতি দেওয়া হয়েছিল — এই একই instruction sequence, ঠিক একই machine code-কে বর্ণনা করে, সম্পূর্ণ ভিন্ন দেখতে একটা টেক্সটেও লেখা সম্ভব:

mov    dword ptr [rbp-4], edi
imul   eax, dword ptr [rbp-4]

দুটো স্নিপেট পাশাপাশি রাখলে প্রথম প্রতিক্রিয়া হতে পারে — “এগুলো কি ভিন্ন instruction?” উত্তর: না, হুবহু একই instruction, একই বাইট — শুধু দুইটা ভিন্ন মানুষী-পাঠযোগ্য কনভেনশনে লেখা। প্রথমটার নাম AT&T syntax, দ্বিতীয়টার Intel syntax

এই বিভাজনটা x86 জগতের একটা ঐতিহাসিক বাস্তবতা — GNU টুলচেইন (gcc, as, gdb, objdump — এই সবকিছুর ডিফল্ট) AT&T ব্যবহার করে, অথচ Intel-এর নিজস্ব official manual, আর জনপ্রিয় assembler NASM/MASM Intel syntax ব্যবহার করে। ফলাফল — একই বিষয়ে দুইজন engineer কথা বলছেন, একজনের screenshot অন্যজনের কাছে “ভুল” মনে হতে পারে, যদিও দুজনেই ঠিক। এই লেসনের লক্ষ্য আপনাকে দুই ভাষাতেই সাবলীল করা — কারণ বাস্তব কাজে (gdb-তে debug করা, একটা ব্লগ পোস্ট পড়া, Compiler Explorer-এ কোড দেখা) দুটোরই মুখোমুখি হবেন, প্রায়ই একই দিনে।

মূল ধারণা

চারটা মূল পার্থক্য

AT&T আর Intel syntax-এর মধ্যে পার্থক্য চারটা নিয়মে সম্পূর্ণভাবে ধরা পড়ে — এই চারটা মুখস্থ থাকলে যেকোনো instruction এক syntax থেকে আরেকটায় যান্ত্রিকভাবে অনুবাদ করা সম্ভব।

নিয়মAT&TIntel
Operand orderSource আগে, destination পরেDestination আগে, source পরে
Register prefix% বাধ্যতামূলক (%rax)কোনো prefix না (rax)
Immediate prefix$ বাধ্যতামূলক ($5)কোনো prefix না (5)
Size নির্দেশনাInstruction mnemonic-এ suffix (movl, movq)Memory operand-এর আগে keyword (dword ptr, qword ptr) — register operand থেকে size এমনিতেই স্পষ্ট

সবচেয়ে বেশি বিভ্রান্তি তৈরি করে operand order — কারণ এটা একটা instruction-এর অর্থ-কে প্রভাবিত করে দেখতে, যদিও প্রকৃতপক্ষে শুধু লেখার নিয়ম:

AT&T:   movq   %rax, %rbx      "rax থেকে rbx-এ কপি করো"   (rbx = rax)
Intel:  mov    rbx, rax        "rbx থেকে rax-এ কপি করো"   (rbx = rax)  -- একই অর্থ!

দুটো লাইনই বলছে rbx = rax — AT&T-তে উৎস (rax) বাম দিকে লেখা হয়, গন্তব্য (rbx) ডানে; Intel-এ ঠিক উল্টো, গন্তব্য বাম দিকে। এটা মুখস্থ করার সহজ উপায় — Intel syntax-কে সাধারণ assignment statement-এর মতো পড়া যায় (rbx = rax, ঠিক x = y-এর মতো বাম দিকে যা বদলাচ্ছে), AT&T-কে পড়তে হয় “থেকে…-তে” (rax থেকে rbx-তে)।

Size suffix বনাম size keyword

AT&T-তে register operand থাকলে register নাম নিজেই size বলে দেয় (%eax মানেই ৩২-বিট) — কিন্তু memory operand-এ, যেখানে কোনো register নেই বোঝানোর জন্য কত byte access করা হবে, mnemonic-এর শেষে একটা অক্ষর যোগ করে সেই তথ্য দেওয়া হয়:

SuffixSizeউদাহরণ
bByte (৮ বিট)movb $5, (%rax)
wWord (১৬ বিট)movw $5, (%rax)
lLong/doubleword (৩২ বিট)movl $5, (%rax)
qQuadword (৬৪ বিট)movq $5, (%rax)

Intel-এ এই একই তথ্য memory operand-এর ঠিক আগে একটা keyword দিয়ে দেওয়া হয় — byte ptr, word ptr, dword ptr, qword ptr:

AT&T:   movb   $5, (%rax)      Intel:  mov    byte ptr [rax], 5
movw   $5, (%rax)              mov    word ptr [rax], 5
movl   $5, (%rax)              mov    dword ptr [rax], 5
movq   $5, (%rax)              mov    qword ptr [rax], 5

যখন উভয় operand register (কোনো memory operand নেই), Intel-এ কোনো size keyword লাগে না — register নিজেই স্পষ্টভাবে size বলে দেয় (mov eax, ebx — কোনো দ্ব্যর্থতা নেই), ঠিক AT&T-র movl %ebx, %eax-এর সমতুল্য।

Memory addressing — সবচেয়ে বড় ভিজ্যুয়াল পার্থক্য

Memory operand লেখার নিয়মটাই সবচেয়ে বেশি চোখে পড়ে ভিন্ন — AT&T বন্ধনী () ব্যবহার করে, Intel বর্গ-বন্ধনী []:

গঠনAT&TIntel
শুধু base register(%rax)[rax]
Displacement + base-4(%rbp)[rbp-4]
Base + index(%rax,%rbx)[rax+rbx]
Base + index×scale(%rax,%rbx,4)[rax+rbx*4]
সব একসাথে-8(%rbp,%rcx,8)[rbp+rcx*8-8]

সাধারণ সূত্র — AT&T-র disp(base,index,scale) আর Intel-এর [base+index*scale+disp] একই তথ্য বহন করে, শুধু চিহ্ন আর ক্রম উল্টো। চূড়ান্ত ঠিকানার সূত্র দুইটাতেই অভিন্ন:

address=base+(index×scale)+displacement\text{address} = \text{base} + (\text{index} \times \text{scale}) + \text{displacement}

এই ঠিকানা-গণনার সূত্রটা গত module-এর addressing-modes লেসনের base+index+scale+displacement আলোচনারই সরাসরি প্রয়োগ — শুধু এখন x86-64-র দুই কংক্রিট syntax-এ লেখা।

x86-64 register set — পূর্ণ তালিকা, sub-register view সহ

গত module-এর registers-pc-flags লেসনে x86-64-র ১৬টা ৬৪-বিট general-purpose register-এর কথা এসেছিল (rax-r15)। এখন সেই সংখ্যাটার পূর্ণ বিস্তার দেখি — প্রতিটা ৬৪-বিট register-এর ভেতরে আসলে একাধিক ওভারল্যাপিং “view” আছে, ৩২/১৬/৮-বিট প্রস্থে — এটাই computer-representation/bits-bytes-words লেসনের bit/byte/word ধারণার সরাসরি, বাস্তব প্রয়োগ।

৬৪-বিট৩২-বিট১৬-বিট৮-বিট (নিচের)৮-বিট (উপরের, শুধু legacy)ঐতিহাসিক ভূমিকা
raxeaxaxalahAccumulator — multiply/divide-এর ফলাফল, function return value
rbxebxbxblbhBase (ঐতিহাসিক indexing)
rcxecxcxclchCounter — loop, shift-count
rdxedxdxdldhData — multiply/divide-এর extension অংশ
rsiesisisilSource index (string operation)
rdiedididilDestination index
rbpebpbpbplBase pointer — stack frame-এর ভিত্তি
rspespspsplStack pointer
r8r15r8dr15dr8wr15wr8br15bx86-64-এ নতুন যোগ হওয়া — কোনো ঐতিহাসিক বিশেষত্ব নেই, শুধু সাধারণ নম্বরিং

চারটা মূল instruction category

গত module-এর ISA আলোচনা মনে করুন — mov (data movement), arithmetic, comparison, control flow — এই একই চার শ্রেণি এখন x86-64-এর concrete mnemonic-এ:

Move — mov

কাজAT&TIntel
Register → registermovq %rax, %rbxmov rbx, rax
Immediate → registermovl $42, %eaxmov eax, 42
Memory → registermovl (%rax), %ebxmov ebx, [rax]
Register → memorymovl %ebx, (%rax)mov [rax], ebx

Arithmetic — add, sub, imul, idiv

কাজAT&TIntel
Addaddq %rax, %rbxadd rbx, rax
Subtractsubq %rax, %rbxsub rbx, rax
Signed multiplyimulq %rax, %rbximul rbx, rax
Signed divideidivq %rbxidiv rbx

idiv-এর একটা বিশেষত্ব — এটা এক-operand instruction, কারণ dividend সবসময় একটা নির্দিষ্ট register-জোড়ায় থাকতে হয় (rdx:rax, উচ্চ+নিম্ন অংশ), আর ফলাফলও নির্দিষ্ট জায়গায় যায় (quotient rax-এ, remainder rdx-এ) — এই কনভেনশন উভয় syntax-এই অভিন্ন, শুধু divisor operand-টাই লেখা হয়।

Comparison — cmp, test

কাজAT&TIntel
Compare (subtraction, ফলাফল বাদ)cmpq %rax, %rbxcmp rbx, rax
Bitwise AND test (ফলাফল বাদ)testq %rax, %raxtest rax, rax

cmp ভেতরে একটা subtraction চালায় (rbx - rax) কিন্তু ফলাফল ফেলে দেয় — শুধু flags (ZF/CF/OF/SF) আপডেট করে। test একই রকম, কিন্তু subtraction-এর বদলে bitwise AND চালায় — test rax, rax একটা সাধারণ idiom “rax কি শূন্য?” চেক করতে (কারণ rax AND rax = rax, ফলাফল শূন্য হলে ZF সেট হয়)।

Control flow — jmp, conditional jump

কাজAT&TIntel
Unconditional jumpjmp .targetjmp target
Jump if equal (ZF=1)je .targetje target
Jump if not equal (ZF=0)jne .targetjne target
Jump if less (signed)jl .targetjl target
Jump if greater (signed)jg .targetjg target

লক্ষ করুন — jump mnemonic-গুলো দুই syntax-এই অভিন্ন লেখা হয় (je, jne, jl, jg — কোনো prefix/suffix পার্থক্য নেই, কারণ এদের কোনো register/immediate/memory operand নেই, শুধু একটা target address, যেটা সাধারণত একটা label)। এই mnemonic-গুলো গত module-এর registers-pc-flags লেসনের flags register (ZF/CF/OF/SF) সরাসরি পড়ে সিদ্ধান্ত নেয় — নিচের হুড সেকশনে flag-সংযোগের পূর্ণ তালিকা দেখব।

পঞ্চম category — Stack manipulation (push, pop)

গত module-এর registers-pc-flags লেসনে stack pointer (rsp)-এর ধারণা এসেছিল — এখন তার concrete instruction:

কাজAT&TIntel
Push (stack-এ রাখা)pushq %raxpush rax
Pop (stack থেকে তোলা)popq %raxpop rax

push %rax আসলে দুইটা কাজ একসাথে করে — rsp কমায় ৮ (একটা quadword, ৬৪-বিটের আকার), তারপর rax-এর মান সেই নতুন rsp-address-এ লেখেpop %rax ঠিক উল্টো — সেই address থেকে পড়ে rax-এ বসায়, তারপর rsp বাড়ায় ৮। দুটোই একক instruction, কিন্তু ভেতরে memory-access + register-arithmetic একসাথে করছে — আরেকটা CISC register-memory উদাহরণ।

ভেতরে কী ঘটছে

নিচে সবসময় একই বাইট — syntax শুধু টেক্সট-স্তরের সিদ্ধান্ত

এই লেসনের সবচেয়ে গুরুত্বপূর্ণ অন্তর্দৃষ্টি — AT&T আর Intel syntax-এর পার্থক্য machine code-এ কোনো ছাপ ফেলে না। Assembler (AT&T ইনপুট নিলে) বা assembler (Intel ইনপুট নিলে, as-এ .intel_syntax noprefix directive দিয়ে) — দুটোই ঠিক একই bit-প্যাটার্ন তৈরি করে গত module-এর instruction-encoding লেসনের সেই একই encoding rule অনুযায়ী। Syntax পার্থক্য সম্পূর্ণভাবে টেক্সট-থেকে-বিট (assembling) আর বিট-থেকে-টেক্সট (disassembling) রূপান্তরের একটা কনভেনশন, নিজে কোনো তথ্য বহন করে না যা machine code-এ প্রতিফলিত হয়।

এটা প্রমাণ করার সবচেয়ে সহজ উপায় — গত লেসনের square.o-কে দুইবার disassemble করা, একবার ডিফল্টে (AT&T), একবার -M intel flag দিয়ে:

objdump -d square.o
0000000000000000 \<square>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d fc                mov    %edi,-0x4(%rbp)
   7:   8b 45 fc                mov    -0x4(%rbp),%eax
   a:   0f af 45 fc             imul   -0x4(%rbp),%eax
   e:   5d                      pop    %rbp
   f:   c3                      ret
objdump -d -M intel square.o
0000000000000000 \<square>:
   0:   55                      push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   89 7d fc                mov    DWORD PTR [rbp-0x4],edi
   7:   8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
   a:   0f af 45 fc             imul   eax,DWORD PTR [rbp-0x4]
   e:   5d                      pop    rbp
   f:   c3                      ret

বাম কলাম (offset আর raw hex byte) হুবহু অভিন্ন দুই আউটপুটে — 55, 48 89 e5, 89 7d fc… একটা বাইটও বদলায়নি। শুধু ডান কলামের mnemonic টেক্সট ভিন্ন — একই বাইটকে দুইভাবে “উচ্চারণ” করা হচ্ছে। এটাই এই লেসনের কেন্দ্রীয় দাবির প্রত্যক্ষ প্রমাণ।

টুল কীভাবে সুইচ করবেন

টুলডিফল্টIntel-এ সুইচ করার কমান্ড
gdbAT&T(gdb) set disassembly-flavor intel
objdumpAT&Tobjdump -M intel ...
GNU asAT&Tফাইলের শুরুতে .intel_syntax noprefix directive
gcc -SAT&Tgcc -S -masm=intel ...
NASMIntel (সবসময়)সুইচ করার দরকার নেই
MASM (Windows)Intel (সবসময়)সুইচ করার দরকার নেই

gdb-তে এটা একটা স্থায়ী সেটিং হিসেবেও রাখা যায় — ~/.gdbinit ফাইলে set disassembly-flavor intel লাইনটা যোগ করলে প্রতিটা নতুন সেশনে স্বয়ংক্রিয়ভাবে Intel syntax ব্যবহৃত হবে। অনেক engineer এটাই করেন, কারণ Intel syntax-কে বেশিরভাগ মানুষ প্রথম পাঠে বেশি স্বজ্ঞাত (intuitive) মনে করেন (assignment-এর মতো পড়া যায় বলে) — কিন্তু GNU-এর ঐতিহাসিক ডিফল্ট AT&T-ই রয়ে গেছে backward-compatibility আর ঐতিহাসিক জড়তার কারণে।

Sub-register লেখার একটা বিপজ্জনক নিয়ম — zero extension

x86-64-র একটা নিয়ম প্রায়ই নতুনদের অবাক করে — ৩২-বিট sub-register-এ লেখা স্বয়ংক্রিয়ভাবে বাকি উপরের ৩২ বিট শূন্য করে দেয় (zero-extends), কিন্তু ১৬-বিট বা ৮-বিট sub-register-এ লেখা তা করে না (উপরের বিট অপরিবর্তিত থাকে)।

mov    eax, 0xFFFFFFFF     ; rax পুরোপুরি হয়ে যায় 0x00000000FFFFFFFF
                            ; -- উপরের ৩২ বিট স্বয়ংক্রিয়ভাবে শূন্য!

mov    ax, 0xFFFF          ; কিন্তু rax-এর উপরের ৪৮ বিট যা ছিল তাই থাকে,
                            ; শুধু নিচের ১৬ বিট বদলায় -- আগের rax যদি
                            ; 0x1234000000000000 থাকত, এখন হবে
                            ; 0x123400000000FFFF

Flags-এর সাথে conditional jump-এর সংযোগ — সম্পূর্ণ তালিকা

গত module-এর registers-pc-flags লেসনে দেখা হয়েছিল signed comparison-এর নিয়ম SF ⊕ OF = 1 মানে “কম” (less)। এখন সেই একই নিয়মের পূর্ণ তালিকা — প্রতিটা conditional jump mnemonic ঠিক কোন flag combination পড়ে:

Mnemonicশর্তFlag এক্সপ্রেশনব্যবহার
je / jzসমান / শূন্যZF = 1if (a == b)
jne / jnzঅসমান / অশূন্যZF = 0if (a != b)
jl / jngeকম (signed)SF ≠ OFif (a \< b) (signed)
jge / jnlকম-না (signed)SF = OFif (a >= b) (signed)
jg / jnleবেশি (signed)ZF = 0 এবং SF = OFif (a > b) (signed)
jle / jngবেশি-না (signed)ZF = 1 অথবা SF ≠ OFif (a \<= b) (signed)
jb / jcকম (unsigned)CF = 1if (a \< b) (unsigned)
jaবেশি (unsigned)CF = 0 এবং ZF = 0if (a > b) (unsigned)

লক্ষ করুন — signed আর unsigned comparison-এর জন্য সম্পূর্ণ ভিন্ন mnemonic ব্যবহার হয় (jl বনাম jb, দুটোই “কম” বোঝায় কিন্তু ভিন্ন flag পড়ে) — এটা গত module-এর সেই signed/unsigned integer representation লেসনের সরাসরি ফলাফল: একই bit-প্যাটার্ন signed বনাম unsigned interpretation-এ ভিন্ন সংখ্যা বোঝাতে পারে, তাই compiler-কে জানতে হয় ভেরিয়েবলের type কী, আর সেই অনুযায়ী সঠিক jump mnemonic বেছে নিতে হয়।

lea — memory স্পর্শ না করেই ঠিকানা গণনা করা

x86-64-র একটা instruction বিশেষভাবে উল্লেখযোগ্য কারণ এটা প্রথম দেখায় বিভ্রান্তিকর — lea (Load Effective Address)। এটার syntax দেখতে memory operand-এর মতো, কিন্তু এটা কোনো memory access করে না — শুধু ঠিকানা-গণনার সূত্র (base + index×scale + displacement) হিসাব করে সেই সংখ্যাটাই register-এ বসায়:

AT&T:   leaq   -4(%rbp), %rax      Intel:  lea    rax, [rbp-4]

এই instruction rax = rbp - 4 করে — কোনো memory read হয় না, শুধু arithmetic। এই কারণেই কম্পাইলাররা lea-কে প্রায়ই একটা সাধারণ arithmetic শর্টকাট হিসেবে ব্যবহার করে, addressing-এর সাথে তার আসল সম্পর্ক ছাড়াই — গত লেসনের example section-এ add function-এর কম্পাইল করা output (lea eax, [rdi+rsi]) ঠিক এই কৌশলটাই ব্যবহার করেছিল a + b গণনা করতে, কোনো প্রকৃত “ঠিকানা”-র প্রয়োজন ছাড়াই — শুধু কারণ lea-র base+index+scale hardware একই cycle-এ একাধিক সাধারণ arithmetic operation (যোগ, ছোট গুণ) একসাথে করতে পারে, যেখানে সাধারণ add একটাই operation করত।

call/ret — কীভাবে ফাংশন-কল কাজ করে, সংক্ষেপে

এই লেসনের উদাহরণগুলোতে ret বারবার এসেছে — এখন তার প্রকৃত hardware আচরণ, কারণ পরের লেসনে এই একই কাজ সম্পূর্ণ ভিন্নভাবে বাস্তবায়িত একটা ISA-তে দেখব।

x86-64-এ call target আসলে দুইটা কাজ একসাথে করে: (১) বর্তমান PC-র পরের instruction-এর ঠিকানা (return address) stack-এ push করে (rsp কমিয়ে, ঠিক push-এর মতোই), (২) তারপর target-এ jump করে (PC-কে সেই ঠিকানায় বসিয়ে)। ret ঠিক উল্টো — stack থেকে সেই ঠিকানা pop করে PC-তে বসায়, ফিরে যায় ঠিক যেখান থেকে call হয়েছিল তার পরের instruction-এ।

AT&T:   call   square             Intel:  call   square
        ...  (এই লাইনে ফেরত আসবে)          ...  (এই লাইনে ফেরত আসবে)

গুরুত্বপূর্ণ পর্যবেক্ষণ, পরের লেসনের জন্য মনে রাখুন — x86-64-এ প্রতিটা call সবসময় stack-এ একটা মান push করে, কোনো ব্যতিক্রম নেই। এমনকি একটা “leaf function” (যে function নিজে আর কাউকে call করে না, যেমন এই লেসনের sum_array) call হলেও, সেই call-এর জন্য stack-এ ৮ byte লেখা আর ret-এর সময় আবার পড়া — এই memory traffic বাধ্যতামূলক, ISA-র সিদ্ধান্তে বেক-ইন করা। পরের লেসনে দেখব ARM64 এই একই সমস্যায় একটা সম্পূর্ণ ভিন্ন সিদ্ধান্ত নিয়েছে।

উদাহরণ

একটা পূর্ণ ফাংশন — দুই syntax-এ পাশাপাশি

একটা array-summing loop দিয়ে সব instruction category একসাথে দেখি — mov, arithmetic, memory addressing, comparison, jump, সবগুলো একটাই function-এ।

int sum_array(int *arr, int n) {
    int total = 0;
    for (int i = 0; i \< n; i++) {
        total += arr[i];
    }
    return total;
}

System V ABI-তে (পরের লেসনগুলোতে বিস্তারিত আসবে) arr আসে rdi-তে, n আসে esi-তে। -O0-স্টাইল (অপ্টিমাইজেশন ছাড়া, প্রতিটা variable stack-এ, সরাসরি পড়া সহজ) দুই syntax-এই:

AT&T syntax                              Intel syntax
────────────────────────────             ────────────────────────────
sum_array:                               sum_array:
    pushq   %rbp                             push    rbp
    movq    %rsp, %rbp                       mov     rbp, rsp
    movq    %rdi, -24(%rbp)                  mov     [rbp-24], rdi
    movl    %esi, -28(%rbp)                  mov     [rbp-28], esi
    movl    $0, -4(%rbp)                     mov     dword ptr [rbp-4], 0
    movl    $0, -8(%rbp)                     mov     dword ptr [rbp-8], 0
.L_loop_cond:                            .L_loop_cond:
    movl    -8(%rbp), %eax                   mov     eax, [rbp-8]
    cmpl    -28(%rbp), %eax                  cmp     eax, [rbp-28]
    jge     .L_loop_end                      jge     .L_loop_end
    movl    -8(%rbp), %eax                   mov     eax, [rbp-8]
    cltq                                      cdqe
    movq    -24(%rbp), %rdx                  mov     rdx, [rbp-24]
    movl    (%rdx,%rax,4), %eax              mov     eax, [rdx+rax*4]
    addl    %eax, -4(%rbp)                   add     [rbp-4], eax
    addl    $1, -8(%rbp)                     add     dword ptr [rbp-8], 1
    jmp     .L_loop_cond                     jmp     .L_loop_cond
.L_loop_end:                             .L_loop_end:
    movl    -4(%rbp), %eax                   mov     eax, [rbp-4]
    popq    %rbp                             pop     rbp
    ret                                       ret
একই sum_array ফাংশন -- AT&T (বামে ধারণাগতভাবে) বনাম Intel (ডানে), পাশাপাশি লাইন-বাই-লাইন।

লাইন-বাই-লাইন, দুই syntax-এই একই গল্প:

  • push rbp / mov rbp, rsp — একটা নতুন stack frame স্থাপন (পরের লেসনের বিষয়, এখানে শুধু pattern হিসেবে চিনে রাখুন)
  • -24(%rbp) / [rbp-24]arr pointer, stack-এ সংরক্ষিত (একটা local variable-এর মতো)
  • .L_loop_cond-এ cmpl -28(%rbp), %eax / cmp eax, [rbp-28] — এটাই i \< n তুলনা, cmp flags সেট করে (ZF/SF/OF), পরের jge সেই flags পড়ে সিদ্ধান্ত নেয় লুপ চালিয়ে যাবে না থামবে
  • cltq / cdqe — একটা sign-extension instruction (eax-এর ৩২-বিট signed মান rax-এ ৬৪-বিটে সম্প্রসারিত করা, array indexing-এ ঠিকানা গণনার জন্য ৬৪-বিট দরকার) — দুই syntax-এই ভিন্ন mnemonic (cltq বনাম cdqe), কিন্তু একই কাজ; এটা একটা বিরল ব্যতিক্রম যেখানে AT&T আর Intel-এর mnemonic নামই আলাদা, শুধু syntax-convention না
  • (%rdx,%rax,4) / [rdx+rax*4]base+index×scale addressing, arr[i]-এর প্রকৃত ঠিকানা গণনা: base (rdx, arr pointer) + index (rax, i) × scale (৪, sizeof(int))
  • addl %eax, -4(%rbp) / add [rbp-4], eax — সরাসরি memory operand-এ arithmetic, x86-64-র CISC register-memory মডেলের একটা উদাহরণ (গত module-এর risc-vs-cisc লেসনের সেই “register-memory” শ্রেণি — একই instruction memory read + add + memory write একসাথে করছে)

নিজে চালিয়ে দেখুন

EXPERIMENT

নিজে objdump/gdb দিয়ে একই বাইট দুই syntax-এ দেখুন

Linux (binutils, gdb) বা WSL/macOS Homebrew· ১৫ মিনিট

গত লেসনের square.o (বা নতুন যেকোনো ছোট .o ফাইল) ব্যবহার করে যাচাই করুন।

# ধাপ ১ -- আগের লেসনের square.o না থাকলে আবার বানান
gcc -c -O0 square.c -o square.o

# ধাপ ২ -- ডিফল্ট (AT&T) disassembly
objdump -d square.o > att_output.txt

# ধাপ ৩ -- Intel syntax-এ disassembly
objdump -d -M intel square.o > intel_output.txt

# ধাপ ৪ -- শুধু hex byte কলাম তুলনা করুন (mnemonic বাদ দিয়ে)
# উভয় ফাইলের বাম দিকের offset+hex byte অংশ কপি করে diff করুন --
# হুবহু মিলবে, ডান দিকের mnemonic টেক্সট আলাদা হলেও

diff \<(grep -oP '^\s+\K[0-9a-f]+:\s+[0-9a-f ]+' att_output.txt) \
     \<(grep -oP '^\s+\K[0-9a-f]+:\s+[0-9a-f ]+' intel_output.txt)
# কোনো আউটপুট মানে কোনো পার্থক্য নেই -- বাইট অভিন্ন

gdb দিয়ে একই পরীক্ষা, ইন্টারেক্টিভভাবে:

gdb ./prog
(gdb) disassemble square      # ডিফল্ট AT&T দেখাবে
(gdb) set disassembly-flavor intel
(gdb) disassemble square      # এখন Intel syntax -- একই function, ভিন্ন টেক্সট
এটা কী প্রমাণ করে

AT&T আর Intel syntax সত্যিই একই বাইটের দুইটা ভিন্ন টেক্সট-উপস্থাপনা মাত্র -- একই object file, দুইবার disassemble করলে বাম কলামের raw hex byte অবিকল একই থাকে, শুধু mnemonic টেক্সট বদলায়।

নিজে বানান

BUILD IT

হাতে অনুবাদ করুন, তারপর assembler দিয়ে যাচাই করুন

x86-64 assembly (as, gcc) · ●●●○○
  1. নিচের ছোট AT&T ফাংশনটা হাতে Intel syntax-এ অনুবাদ করুন, কাগজে বা টেক্সট এডিটরে
  2. অনুবাদটা .intel_syntax noprefix directive দিয়ে একটা .s ফাইলে লিখুন
  3. as দিয়ে সরাসরি অ্যাসেম্বল করুন, আর মূল AT&T সংস্করণও আলাদাভাবে অ্যাসেম্বল করুন
  4. objdump -d দিয়ে দুইটা object file-এর machine code বাইট তুলনা করুন
  5. যদি বাইট না মেলে, ঠিক কোন লাইনে ভুল হয়েছে খুঁজে বের করুন -- সাধারণত operand-order বা memory-addressing ভুল

মূল ফাংশন (AT&T, ইতিমধ্যে দেওয়া):

mymax:
    movl    %edi, %eax
    cmpl    %esi, %edi
    jge     .done
    movl    %esi, %eax
.done:
    ret

এটা একটা int mymax(int a, int b)a কে edi-তে ধরে নিয়ে eax-এ কপি করে (ধরে নেওয়া হচ্ছে এটাই উত্তর হবে), তারপর a >= b (signed) হলে সরাসরি ret, নাহলে eax-কে b-তে ওভাররাইট করে।

আপনার কাজ — এটাকে Intel syntax-এ লিখুন (মনে রাখুন: operand order উল্টান, %/$ prefix বাদ দিন), তারপর:

# Intel সংস্করণ (আপনার তৈরি করা mymax_intel.s ফাইলে .intel_syntax noprefix
# directive দিয়ে শুরু করতে হবে)
as mymax_intel.s -o mymax_intel.o

# AT&T সংস্করণ (উপরের কোড, mymax_att.s-এ)
as mymax_att.s -o mymax_att.o

# তুলনা
objdump -d mymax_att.o
objdump -d mymax_intel.o
# বাম কলামের hex byte হুবহু মেলা উচিত

সঠিক Intel অনুবাদ (নিজে চেষ্টা করার পর মিলিয়ে দেখুন):

.intel_syntax noprefix
mymax:
    mov     eax, edi
    cmp     edi, esi
    jge     .done
    mov     eax, esi
.done:
    ret

বাস্তব সিস্টেমে

যেখানে এই দুই syntax প্রতিদিন মুখোমুখি হয়

gdb — ডিফল্ট AT&T, কিন্তু বহু ব্যবহারকারী সুইচ করে নেন। Linux-এ debugging করার সময় disas কমান্ড ডিফল্টে AT&T দেখায় — অনেক engineer তাদের .gdbinit-এ set disassembly-flavor intel যোগ করে রাখেন, এই লেসনে দেখানো ঠিক এই কমান্ডটাই।

perf annotate — profiling-এ disassembly। Linux-এর perf টুল দিয়ে একটা hot function profile করার সময়, perf annotate cycle-খরচ দেখায় প্রতিটা instruction-এর পাশে — এই disassembly ডিফল্টে AT&T (objdump-ভিত্তিক টুলিং হওয়ায়), তাই Level 11-এর performance module-এ এই syntax-জ্ঞান সরাসরি কাজে লাগবে।

NASM — bootloader/OS-dev-এর জনপ্রিয় পছন্দ। Netwide Assembler Intel syntax ব্যবহার করে, আর OS development/bootloader লেখার কমিউনিটিতে সবচেয়ে জনপ্রিয় assembler — কারণ readability-র সুবিধা, আর Intel-এর নিজস্ব manual-এর সাথে সরাসরি মিল থাকায় hardware-স্তরের ডকুমেন্টেশন পড়া সহজ হয়।

MASM — Windows-এর নিজস্ব assembler। Microsoft Macro Assembler, Windows-নেটিভ ডেভেলপমেন্টে ব্যবহৃত, Intel syntax-এর আরেকটা বাস্তবায়ন — Windows-কেন্দ্রিক নিম্ন-স্তরের কোডে সাধারণত Intel syntax-ই প্রধান রীতি।

Linux kernel-এর inline assembly — ঐতিহাসিকভাবে AT&T। Linux kernel C কোডে inline assembly (asm volatile(...)) GCC-র ডিফল্ট (AT&T) ব্যবহার করে — যদিও -masm=intel দিয়ে পুরো compilation Intel-এ সুইচ করা সম্ভব, kernel community ঐতিহাসিকভাবে AT&T-তেই রয়ে গেছে সামঞ্জস্যের জন্য।

Intel SDM — প্রতিটা instruction-এর প্রামাণ্য উৎস, Intel syntax-এ। যখনই একটা instruction-এর ঠিক কী encoding, কী flag-প্রভাব, তা নিশ্চিত হতে চান, Intel Software Developer’s Manual-ই চূড়ান্ত উৎস — আর সেটা স্বাভাবিকভাবেই Intel syntax-এ লেখা।

Rosetta 2 (macOS) আর বাইনারি ট্রান্সলেটর — একটা ভিন্ন স্তরের “অনুবাদ”। এই লেসনের syntax-অনুবাদ শুধু টেক্সট-স্তরের (একই বাইট, ভিন্ন উপস্থাপনা)। Apple-এর Rosetta 2 সম্পূর্ণ ভিন্ন, অনেক গভীর কাজ করে — x86-64 machine code-কে সত্যিকারের ভিন্ন ARM64 machine code-এ রূপান্তর করে (পরের লেসনের বিষয়) — একটা ভালো contrast মনে রাখার জন্য: syntax-অনুবাদ vs ISA-অনুবাদ সম্পূর্ণ ভিন্ন মাত্রার সমস্যা।

Ghidra/IDA Pro — reverse engineering টুল, উভয় syntax-ই সমর্থন করে। Security research আর malware analysis-এ ব্যবহৃত এই টুলগুলো ব্যবহারকারীকে AT&T/Intel-এর মধ্যে টগল করতে দেয় — এই লেসনের দক্ষতা সরাসরি Level 10-এর security module-এর reverse-engineering কাজে প্রয়োজন হবে।

Stack buffer overflow — call/ret-এর একটা নিরাপত্তা-তাৎপর্য। যেহেতু call return address stack-এ push করে আর ret সেখান থেকেই পড়ে PC আপডেট করে, একটা classic buffer-overflow আক্রমণ ঠিক এই মেকানিজমকেই লক্ষ্য করে — যদি একটা stack-এ থাকা buffer-এ (যেমন একটা fixed-size array) তার আকারের চেয়ে বেশি ডেটা লেখা যায় (bounds-checking ছাড়া কোড), লেখাটা buffer-এর সীমা পেরিয়ে ঠিক সেই সংরক্ষিত return-address-এর উপর গিয়ে পড়তে পারে — attacker তখন ret-কে তার নিজের পছন্দমতো ঠিকানায় পাঠাতে পারে। Level 10-এর security module এই আক্রমণ, আর তার প্রতিরোধ (stack canary, ASLR, NX bit) বিস্তারিতভাবে কভার করবে — কিন্তু সেই আলোচনা বোঝার পূর্বশর্তই এই লেসনের call/push/ret mechanism।

যে ভুলগুলো সবাই করে

“AT&T আর Intel syntax দুইটা ভিন্ন instruction set -- একটাতে যা করা যায় আরেকটাতে করা যায় না।”

এই লেসনের hood সেকশনেই সরাসরি প্রমাণ দেখানো হয়েছে এর বিপরীত — objdump -d বনাম objdump -d -M intel একই square.o ফাইলে চালিয়ে বাম কলামের raw hex byte হুবহু অভিন্ন পাওয়া গেছে।

Syntax শুধু একটা টেক্সট-কনভেনশন — assembler (টেক্সট থেকে বাইটে) আর disassembler (বাইট থেকে টেক্সটে) রূপান্তরের নিয়ম। উভয় syntax-ই x86-64-র সম্পূর্ণ একই instruction set বর্ণনা করে — কোনো instruction একটাতে “সম্ভব” আরেকটাতে “অসম্ভব” এমন কিছু নেই। যা AT&T-তে লেখা যায়, তার একটা হুবহু সমতুল্য Intel-এও লেখা যায়, আর উল্টোটাও।

“Operand order উল্টে যাওয়াটা নেহাত একটা স্টাইলের ব্যাপার -- মনোযোগ না দিলেও চলে, কারণ পরে বোঝা যাবে।”

এই লেসনের concept সেকশনের warn Callout-এই এই ভুল ধারণার সরাসরি প্রতিষেধক দেওয়া হয়েছিল — operand order উপেক্ষা করলে উৎস আর গন্তব্য উল্টে বোঝার বাস্তব ঝুঁকি থাকে।

mov eax, ebx (Intel) আর movl %ebx, %eax (AT&T) — দুটোই ঠিক একই কাজ করে (eax = ebx), কিন্তু যদি আপনি AT&T-অভ্যস্ত হয়ে Intel-syntax কোড পড়ার সময় পুরনো “বাম দিকে source” অভ্যাস প্রয়োগ করেন, আপনি ভাববেন ebx = eax — সম্পূর্ণ উল্টো সিদ্ধান্ত। একটা debugging session-এ এই ভুল একটা সম্পূর্ণ ভুল দিকে অনুসন্ধান পাঠাতে পারে — কোন register-এ আসলে কী মান আছে তা ভুল বুঝলে পুরো trace-ই অর্থহীন হয়ে যায়।

“একবার একটা syntax শিখলে, অন্যটার সাথে কখনো কাজ করতে হবে না -- একটাই যথেষ্ট।”

বাস্তব পেশাদার কাজে এই ধারণা টেকে না। GNU টুলচেইন (Linux-এর সবচেয়ে ব্যবহৃত gcc/gdb/objdump) ডিফল্টে AT&T, কিন্তু Intel-এর নিজস্ব ডকুমেন্টেশন, বহু ব্লগ পোস্ট/টিউটোরিয়াল, আর Windows-কেন্দ্রিক টুলিং (NASM, MASM) Intel syntax-এ — একজন systems engineer বাস্তবে দুটোরই মুখোমুখি হবেন, প্রায়ই একই সপ্তাহে (হয়তো একদিন gdb-তে ডিবাগ করছেন AT&T-তে, পরদিন একটা Intel manual পড়ছেন একটা instruction-এর সঠিক আচরণ নিশ্চিত করতে)।

এই লেসনের লক্ষ্যই ছিল দুই syntax-এ সাবলীলভাবে অনুবাদ করতে পারা — কোনোটাকেই “একমাত্র সত্যিকারের” syntax হিসেবে ধরে না নিয়ে।

“Intel syntax 'বেশি সঠিক' কারণ Intel-ই তো x86 বানিয়েছে -- AT&T একটা 'বিকৃত' বা 'দ্বিতীয় সারির' সংস্করণ।”

এই ধারণাটা ঐতিহাসিকভাবে ভুল, আর দুই syntax-কে অপ্রয়োজনীয়ভাবে “সঠিক বনাম ভুল” এর মধ্যে ফেলে দেয়। AT&T syntax এসেছে Unix-এর মূল assembler ঐতিহ্য থেকে (Bell Labs, ১৯৭০-এর দশক) — x86 আসার অনেক আগে থেকেই এই কনভেনশন Unix-এর বিভিন্ন CPU architecture-এ ব্যবহৃত হচ্ছিল (source-আগে-destination-পরে, register prefix দিয়ে টাইপ স্পষ্ট করা)। যখন GNU টুলচেইন x86 সমর্থন যোগ করল, তারা এই একই, ইতিমধ্যে-প্রতিষ্ঠিত Unix-assembler কনভেনশন অনুসরণ করল — Intel-এর নতুন official manual-এর syntax অনুসরণ করেনি।

দুটোই সমান বৈধ, সমান “সঠিক” — একটাই instruction set-এর দুইটা স্বাধীনভাবে-বিকশিত টেক্সট-প্রতিনিধিত্ব, একটা Unix-ঐতিহ্য থেকে, আরেকটা Intel-এর নিজস্ব ডকুমেন্টেশন-ঐতিহ্য থেকে। কোনোটা অন্যটার “থেকে নেওয়া বিকৃতি” না — দুটোই সমান্তরালভাবে বিকশিত হয়েছে।

বুঝেছেন কি না দেখুন

1

নিচের AT&T instruction-টা Intel syntax-এ অনুবাদ করুন: subq %rbx, %rax

স্মরণ
sub    rax, rbx

যুক্তি: AT&T-তে subq %rbx, %rax মানে “source %rbx, destination %rax” — অর্থাৎ rax = rax - rbx। Operand order উল্টে (Intel-এ destination আগে) পাই sub rax, rbx, আর q suffix বাদ দিলাম কারণ Intel-এ register operand নিজেই ৬৪-বিট স্পষ্ট করে দেয় (rax/rbx — কোনো size keyword লাগেনি, শুধু memory operand হলে লাগত)।

2

নিচের দুইটা লাইন একই function-এর দুইটা ভিন্ন সংস্করণ থেকে (একই compiler, ভিন্ন -masm flag)। দুটোই কি একই কাজ করে? ব্যাখ্যা করুন।

সংস্করণ ১ (AT&T):   movl   %eax, %ebx
সংস্করণ ২ (Intel):  mov    eax, ebx
প্রয়োগ

না, এই দুইটা লাইন একই কাজ করে না — এটা এই লেসনের সবচেয়ে গুরুত্বপূর্ণ সতর্কতার একটা সরাসরি পরীক্ষা।

সংস্করণ ১ (AT&T) — movl %eax, %ebx — operand order source-আগে-destination-পরে, তাই এটা মানে ebx = eax (eax-এর মান ebx-এ কপি হচ্ছে)।

সংস্করণ ২ (Intel) — mov eax, ebx — operand order destination-আগে-source-পরে, তাই এটা মানে eax = ebx (ebx-এর মান eax-এ কপি হচ্ছে) — একেবারে উল্টো দিক!

এই দুইটা একই compiler থেকে “একই function”-এর ভিন্ন -masm output হলে, প্রকৃতপক্ষে সঠিক অনুবাদ হতো সংস্করণ ১-এর জন্য Intel-এ mov ebx, eax (operand উল্টে), সংস্করণ ২-এর AT&T-সমতুল্য হতো movl %ebx, %eax। প্রশ্নে দেওয়া জোড়াটা প্রকৃতপক্ষে দুইটা ভিন্ন operation বর্ণনা করছে, একই না — এটাই সেই বিপজ্জনক ফাঁদ যেখানে দুই syntax-এর operand একই ক্রমে (eax, ebx) লিখলেও তারা আসলে ভিন্ন জিনিস বোঝায়।

3

নিচের কোড চালানোর পর rax-এর চূড়ান্ত মান কত (hex-এ), ধরে নিন শুরুতে rax = 0x123456789ABCDEF0?

mov    eax, 0x11111111
যুক্তি

rax = 0x0000000011111111

এই লেসনের hood সেকশনের zero-extension নিয়ম সরাসরি প্রযোজ্য — eax (৩২-বিট sub-register) লেখা স্বয়ংক্রিয়ভাবে rax-এর উপরের ৩২ বিট শূন্য করে দেয়, শুধু নিচের ৩২ বিট বদলায় না, পুরো ৬৪-বিট register-এর একটা পরিষ্কার, পূর্বাবস্থা-নিরপেক্ষ মান তৈরি হয়।

তাই শুরুর rax = 0x123456789ABCDEF0 থেকে mov eax, 0x11111111 চালানোর পর:

  • নিচের ৩২ বিট নতুন মান নেয়: 0x11111111
  • উপরের ৩২ বিট শূন্য হয়ে যায় (আগের 0x12345678 হারিয়ে যায়, রয়ে যায় না)

চূড়ান্ত ফলাফল: 0x0000000011111111

তুলনা করুন — যদি একই শুরুর অবস্থা থেকে mov ax, 0x1111 (১৬-বিট sub-register) চালানো হতো, ফলাফল হতো 0x123456781111DEF0-এর মতো কিছু (শুধু নিচের ১৬ বিট বদলাত, উপরের ৪৮ বিট অক্ষত থাকত) — কারণ ১৬-বিট write zero-extend করে না।

4

Signed integer a আর b (দুটোই int) নিয়ে if (a \< b) কম্পাইল হয়ে x86-64-এ। কম্পাইলার কোন দুইটা instruction generate করবে (mnemonic নাম বলুন), আর দ্বিতীয়টা ঠিক কোন flag পড়ে সিদ্ধান্ত নেয়?

প্রয়োগ

কম্পাইলার generate করবে cmp (তুলনা, flags সেট করতে) তারপর jl (jump if less, signed) — যেমন:

AT&T:    cmpl   %esi, %edi         Intel:   cmp    edi, esi
         jl     .true_branch                jl     .true_branch

(এখানে ধরা হয়েছে a আছে edi/esi-জোড়ার প্রথমটায়, b দ্বিতীয়টায় — নির্দিষ্ট register assignment compiler ও calling convention-এর উপর নির্ভরশীল।)

jl এই লেসনের flag-টেবিল অনুযায়ী পড়ে SF ≠ OF (sign flag আর overflow flag অসমান হলে jump নেয়) — এটাই signed “কম” (less than) নির্ধারণের সঠিক নিয়ম, গত module-এর registers-pc-flags লেসনে derive করা SF ⊕ OF = 1 সূত্রেরই এই লেসনের mnemonic-স্তরের প্রয়োগ।

গুরুত্বপূর্ণ — যদি a, b unsigned int হতো, কম্পাইলার jl-এর বদলে jb (jump if below, unsigned) generate করত, যেটা CF = 1 পড়ে — সম্পূর্ণ ভিন্ন flag, কারণ unsigned তুলনার “কম” মানে ভিন্ন hardware-সিদ্ধান্ত (কোনো sign bit বিবেচনায় নেই, শুধু raw bit-প্যাটার্নের সংখ্যাগত তুলনা, borrow/carry দিয়ে নির্ধারিত)।

5

আপনি একটা নতুন টিম সদস্যকে onboard করছেন যিনি শুধু Intel syntax চেনেন (NASM-এ শিখেছেন), কিন্তু আপনার টিম Linux-এ gdb/objdump (ডিফল্ট AT&T) দিয়ে দৈনন্দিন ডিবাগিং করে। দুইটা বাস্তবসম্মত সমাধান প্রস্তাব করুন যাতে তিনি দ্রুত productive হতে পারেন, প্রতিটার একটা trade-off উল্লেখ করুন।

ডিজাইন

সমাধান ১ — টুল-স্তরে সুইচ করা (set disassembly-flavor intel / objdump -M intel)।

এই লেসনের hood সেকশনে দেখানো কমান্ডগুলো ব্যবহার করে টিম সদস্যের নিজের .gdbinit-এ স্থায়ীভাবে Intel syntax সেট করে দেওয়া, আর objdump -M intel কে একটা shell alias (alias objdump-i='objdump -M intel') বানিয়ে দেওয়া। সুবিধা: তার পরিচিত syntax-এই সব দেখতে পারবেন, দ্রুত productive হবেন, কোনো নতুন শেখার বাধা নেই। Trade-off: টিমের বাকি সদস্যরা AT&T-তে কথা বলে/screenshot শেয়ার করে থাকলে, communication-এ ঘর্ষণ থাকবে (“আমার screen-এ যা দেখাচ্ছে, তোমার screen-এ ভিন্ন দেখাবে”) — pair debugging session-এ বিভ্রান্তি হতে পারে যদি দুইজন ভিন্ন syntax দেখছেন অথচ বুঝতে পারছেন না কেন একে অপরের বর্ণনা মিলছে না।

সমাধান ২ — তাকে AT&T শিখিয়ে দেওয়া, এই লেসনের অনুবাদ-নিয়ম ব্যবহার করে।

চারটা নিয়ম (operand order, %/$ prefix, size suffix, addressing bracket) শিখিয়ে, তাকে কয়েকটা compiled function হাতে অনুবাদ করতে দেওয়া অনুশীলন হিসেবে (এই লেসনের BuildIt-এর মতোই)। সুবিধা: পুরো টিম একই syntax-এ কথা বলে, কোনো communication-ঘর্ষণ নেই, আর team member নিজেও দুই syntax-এ সাবলীল হয়ে ওঠেন (দীর্ঘমেয়াদে বেশি মূল্যবান দক্ষতা, যেহেতু বাস্তব কাজে উভয়েরই মুখোমুখি হবেন)। Trade-off: স্বল্পমেয়াদে ধীর — শেখার একটা সময়কাল লাগবে যেখানে productivity তুলনামূলক কম, আর initial ভুল (operand-order গুলিয়ে ফেলা) হওয়ার সম্ভাবনা থাকবে যতক্ষণ না অভ্যাস তৈরি হয়।

বাস্তব সুপারিশ: দুটোই একসাথে করা সবচেয়ে ব্যবহারিক — শুরুতে সমাধান ১ (Intel-এ সুইচ করে দ্রুত productive হওয়া), সমান্তরালে সমাধান ২-এর অনুশীলন চালিয়ে যাওয়া, যাতে ধীরে ধীরে AT&T-তেও সাবলীলতা তৈরি হয় এবং টিমের বাকিদের সাথে communication সহজ হয়।

6

leaq 8(%rax,%rbx,4), %rcx (AT&T) — এই instruction চালানোর ফলে কি কোনো memory-তে read/write ঘটে? rcx-এ ঠিক কী মান বসবে (একটা সূত্র লিখুন), আর Intel syntax-এ এটা কেমন দেখাবে?

যুক্তি

না, কোনো memory access ঘটে নাlea (Load Effective Address) নামে “Load” থাকলেও এটা memory থেকে কিছু পড়ে না বা লেখে না, শুধু একটা ঠিকানা-গণনার arithmetic সূত্র হিসাব করে সেই সংখ্যাটা register-এ বসায় — এই লেসনের hood সেকশনের কেন্দ্রীয় পয়েন্ট।

rcx-এ বসা মান:

rcx=rax+(rbx×4)+8\text{rcx} = \text{rax} + (\text{rbx} \times 4) + 8

এটা ঠিক base+index×scale+displacement addressing-এর সাধারণ সূত্র (base + index×scale + disp), যেখানে rax = base, rbx = index, 4 = scale, 8 = displacement — কিন্তু ফলাফল ঠিকানা হিসেবে ব্যবহৃত হচ্ছে না, সরাসরি rcx-এ একটা সাধারণ সংখ্যা হিসেবে বসছে।

Intel syntax-এ:

lea    rcx, [rax+rbx*4+8]

Operand order উল্টে (destination আগে), %/$ prefix বাদ দিয়ে, আর addressing bracket () থেকে []-এ বদলে — এই লেসনের চারটা মূল অনুবাদ-নিয়মের প্রত্যক্ষ প্রয়োগ।

এরপর কী

পরের লেসন — ARM64 (AArch64) Basics

এই পুরো লেসন জুড়ে x86-64-র একটা বৈশিষ্ট্য বারবার সামনে এসেছে যা হয়তো লক্ষ্য করেছেন — memory operand সরাসরি arithmetic instruction-এ ব্যবহার করা যায় (add [rbp-4], eax — একই instruction memory read + add + memory write করছে)। এটা x86-64-র CISC-heritage-এর একটা সরাসরি প্রকাশ, গত module-এর risc-vs-cisc লেসনের “register-memory architecture” শ্রেণির উদাহরণ।

পরের লেসনে আমরা সম্পূর্ণ বিপরীত দর্শনের একটা ISA দেখব — ARM64 (AArch64), একটা বিশুদ্ধ load-store architecture, যেখানে arithmetic instruction কখনোই সরাসরি memory স্পর্শ করে না, শুধু ldr/str করে। ARM64-র আরেকটা বড় স্বস্তির খবর — এখানে AT&T-বনাম-Intel-এর মতো কোনো ঐতিহাসিক syntax-বিভাজনই নেই, একটাই canonical syntax সবখানে ব্যবহৃত হয়। সাথে দেখব ARM64-র রেজিস্টার-এলিয়াসিং (x0/w0 — x86-এর অসমমিত al/ax/eax/rax-এর চেয়ে অনেক সরল), আর সেই বিখ্যাত link register (x30/lr) — function return address সামলানোর একটা সম্পূর্ণ ভিন্ন, RISC-ধাঁচের কৌশল, যা x86-64-র সবসময়-stack-এ-push-করা call/ret থেকে সত্যিই আলাদা একটা perf-প্রাসঙ্গিক ডিজাইন-সিদ্ধান্ত।

আরও পড়ুন

  • Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2 — Instruction Set Reference — Intel Corporation · প্রতিটা instruction-এর প্রামাণ্য Intel-syntax বর্ণনা — এই লেসনের সব instruction encoding এখান থেকে যাচাইযোগ্য
  • GNU Assembler (as) Manual — x86 Dependent Features · AT&T syntax-এর প্রামাণ্য নিয়ম, আর .intel_syntax directive-এর ব্যবহার
  • System V Application Binary Interface, AMD64 Architecture Processor Supplement · x86-64 register naming ও calling convention-এর প্রামাণ্য উৎস, পরের লেসনগুলোর ভিত্তি
  • Compiler Explorer (godbolt.org) · এই লেসনের experiment-এ AT&T/Intel টগল করে একই কোড দেখার জন্য ব্যবহৃত