Pseudocode में उठानाः IR, SSA, और स्क्रैच से संरचना
Type: Build
Languages: Python, C
Prerequisites: Phase 13 · 32 (ELF from scratch), Phase 13 · 33 (disassembly and control flow)
Time: ~150 minutes
सीखने के लक्ष्य
- लिफ्ट ने स्पष्ट ध्वज, लोड, स्टोर, शाखाओं, कॉल और रिटर्न के साथ एक छोटे आईआर में x86-64 निर्देशों को डिकोड किया।
- फ्रेम स्लॉट को स्थानीय चर और सिस्टम V तर्क रजिस्टर को पैरामीटर में बदल दें।
- प्रभुत्व सीमाओं पर फ्हि फ़ंक्शंस के साथ न्यूनतम एसएसए बनाएं, फिर मृत कोड को प्रतिस्थापित, सरल और हटा दें।
- ठीक हो जाओ
if,ifके साथelse,whileऔरdo whileपोस्ट-डोमिनटर्स और बैक किनारों के साथ नियंत्रण प्रवाह ग्राफ से। - गुणा को समझाएँ
0x6666666710 से विभाजित होने के रूप में और पहचान साबित करें। - एक ही फ़ंक्शन के लिए गिद्रा स्यूडोकोड को लाइन-लाइन पढ़ें और प्रत्येक अंतर के पीछे निर्देश या अनुमान का नाम दें।
समस्या
आपने रिया से प्यूडोकोड के लिए पूछा थाpercent_changeमें ledger-x86_64-O1.o. गिदरा 12.1.4 ने लगभग दस सेकंड में जवाब दियाः
cundefined1 [16] percent_change(int param_1,int param_2,ulong param_3)
{
long lVar1;
undefined1 auVar2 [16];
undefined1 auVar3 [16];
if (param_1 != 0) {
lVar1 = (long)((param_2 - param_1) * 100);
auVar3._0_8_ = lVar1 / (long)param_1 & 0xffffffff;
auVar3._8_8_ = lVar1 % (long)param_1 & 0xffffffff;
return auVar3;
}
auVar2._8_8_ = 0;
auVar2._0_8_ = param_3;
return auVar2 << 0x40;
}स्रोत चार पंक्तियों की लंबाई है. यह दो लेता है.intमान और रिटर्न एक int. गिड्रा एक 16-बाइट रिटर्न मान और एक तीसरा पैरामीटर रिपोर्ट करता है जो स्रोत में नहीं है। मशीन कोड के सोलह बाइट सही हैं, और गिड्रा ने उनमें से प्रत्येक को सही ढंग से डिकोड किया है। हस्ताक्षर एक अनुमान है, और पाठ में कुछ भी इसे एक के रूप में चिह्नित नहीं करता है।
rea इस पाठ को वापस करता है procedure_pseudo_codeपरिणाम और रिकॉर्ड के लिए एक सीमा संलग्न करता है। सीमा पढ़ता है "Pseudocode Ghidra डिसीम्पिलर उत्पादन है, मूल स्रोत नहीं है और हॉपर उत्पादन के लिए पाठ-बराबर नहीं है। "compare_functionsउपकरण पाठ की पंक्तियों के रूप में छद्म कोड को अलग करता है। इसका अपना आयाम रिकॉर्ड कहता है "एसेम्ब्ली अपारदर्शी प्रदाता पाठ है" और यह स्थानांतरण सामान्यीकरण अनुपलब्ध है। rea पाठ के अंदर नहीं देख सकता है, और न ही एक एजेंट जो केवल इसे पढ़ता है।
आरईए के डीएक्स-बल रिकॉर्ड लागत को दर्शाता है. पहले decompilator परिणाम थाFUN_00406400(void)बुला रहा है__ftol()निर्देश दृश्य में एक स्टैक इनपुट और एक x87 अभिव्यक्ति दिखाई दी। एजेंट केवल इसलिए चलता रहा क्योंकि दोनों दृश्य असहमत थे।
इस पाठ में व्युत्पन्न का निर्माण किया जाता है कि डिकोम्पाइलर छिपाता है. आप 56 निर्देशों से शुरू करते हैं कि पाठ 33 डिकोड किया, उन्हें एक आईआर तक उठाने, नाम बदलकर एसएसए, सरल, और संरचना में. फिर आप अपने छद्म कोड Ghidra के बगल में रखोpercent_change,gradeऔर totalपरीक्षणों को आपके आउटपुट को क्लिंग के साथ संकलित करें और इसे इनपुट ग्रिड पर स्रोत के खिलाफ चलाएं।
फिक्स्चर और ओरेकल
code/fixtures/ledger.cपाठ्यक्रम के इस भाग का साझा संयोजन हैः
cint percent_change(int before, int after) {
if (before == 0) return 0;
return (after - before) * 100 / before;
}
int grade(int score) {
switch (score / 10) {
case 10:
case 9: return 4;
case 8: return 3;
case 7: return 2;
case 6: return 1;
default: return 0;
}
}
int total(const int *values, int count) {
int sum = 0;
for (int i = 0; i < count; i++) sum += values[i];
return sum;
}grade_labelयह फ़ाइल और ऑब्जेक्ट में भी है, और डेमो इसे भी उठाता है. ऊपर तीन कार्यों के लिए है जो इस पाठ परीक्षण है.
वस्तु का निर्माण किया गया था clang -target x86_64-unknown-linux-gnu -O1 -fno-asynchronous-unwind-tables -c ledger.c. . .-O0वस्तु स्टैक-स्लॉट अनुभाग के लिए इसके बगल में बैठता है। code/fixtures/README.mdप्रत्येक वस्तु के SHA-256 और Ghidra फिक्स्चर उत्पन्न करने वाली rea कमांड दोनों कमांडों की सूची देता है। निर्माण पिन टूल्सचेन पर बाइट के लिए पुनः प्रयोज्य बाइट है।
डिकोडर के लिए Oracle है llvm-objdump -d -r. दोनों वस्तुओं के लिए इसका पाठ इस प्रकार प्रतिबद्ध हैledger-x86_64-O1.objdump.txtऔर ledger-x86_64-O0.objdump.txt. एक परीक्षण उन फ़ाइलों का विश्लेषण करता है और प्रत्येक निर्देश को पते, बाइट्स और एटी एंड टी पाठ द्वारा तुलना करता है। यह सभी 56 निर्देशों को कवर करता है।-O1और हर एक निर्देश-O0. . जब llvm-objdumpएक दूसरे परीक्षण को चलाता है और फिर से तुलना करता है।
यहाँ हैpercent_changeजैसा कि प्रवचन इसे प्रिंट करता हैः
text0000000000000000 <percent_change>:
0: 85 ff testl %edi, %edi
2: 74 09 je 0xd <percent_change+0xd>
4: 29 fe subl %edi, %esi
6: 6b c6 64 imull $0x64, %esi, %eax
9: 99 cltd
a: f7 ff idivl %edi
c: c3 retq
d: 31 c0 xorl %eax, %eax
f: c3 retqचरण 1: निर्देशों के लिए बाइट्स
पाठ 33 ने डिकोडर बनाया। यह पाठ एक छोटी कॉपी रखता है ताकि कोड अपने आप चलता है। कॉपी ठीक उसी ऑपकोड को कवर करती है जो क्लिंग के लिए जारी किया गया था।ledger.cपर-O0और -O1. . और कुछ भी यह उठाता है के लिए .DecodeErrorपते और ओपकोड बाइट के साथ।
डिकोडर इंटेल एसडीएम, वॉल्यूम 2, खंड 2.1 के बाइट क्रम का पालन करता है। पूर्वावलोकन पहले आते हैं, फिर ओपकोड, फिर मॉडआर / एम, फिर एसआईबी, फिर विस्थापन, फिर तत्काल। खंड 2.2.1 में REX पूर्वावलोकन जोड़ा जाता है, से एक बाइट।0x400x4FREX.W एक 64-बिट ऑपरेंड का चयन करता है, REX.R विस्तारित करता है regक्षेत्र, REX.X SIB सूचकांक का विस्तार करता है, और REX.B विस्तार करता है r/mधारा 2.1.3 में तीन मॉडआर/एम फ़ील्ड्स की परिभाषा की गई है mod,regऔर r/m, और तीन SIB क्षेत्रों scale,indexऔर base. 64-बिट मोड में, mod = 00के साथr/m = 101अगले निर्देश के सापेक्ष 32-बिट विस्थापन (धारा 2.2.1.6) ।
| Opcode bytes | Form | Fixture example |
|---|---|---|
01 29 31 39 85 89 | op Ev, Gv | 29 fe subl %edi, %esi |
03 2b 3b 8b 8d | op Gv, Ev | 03 04 97 addl (%rdi,%rdx,4), %eax |
63 with REX.W | movsxd Gv, Ev | 48 63 c7 movslq %edi, %rax |
69 and 6b | imul Gv, Ev, imm | 48 69 c0 67 66 66 66 |
83 /0 /5 /7 | add sub cmp Ev, imm8 | 83 c1 fa addl $-0x6, %ecx |
c1 /5 /7 | shr sar Ev, imm8 | 48 c1 f9 22 sarq $0x22, %rcx |
f7 /7 | idiv Ev | f7 ff idivl %edi |
ff /0 /4 | inc Ev, jmp Ev | 48 ff c2 incq %rdx |
70 to 7f, eb, e8 | jcc rel8, jmp rel8, call rel32 | 74 09 je 0xd |
99, c3, 90, 0f 1f | cltd, retq, nop | 66 0f 1f 44 00 00 nopw (%rax,%rax) |
leaq (%rip), %rcxपरgrade+0x23शून्य विस्थापन के साथ सात बाइट है। वस्तु एक स्थानांतरण ले जाता हैR_X86_64_PC32.rodata+0x10विलय में0x36लिफ्टर इसे चरण 3 में हल करता है।
चरण 2: ब्लॉक और डोमिनैटर
एक बुनियादी ब्लॉक एक बयानों का एक अनुक्रम है जिसमें शीर्ष पर एक प्रविष्टि और नीचे एक आउटपुट है। यह एलन (1970) की परिभाषा है, और प्रत्येक डिकोम्पाइलर पेपर इसे पुनः उपयोग करता है। build_blocks()फ़ंक्शन प्रविष्टि, फ़ंक्शन के अंदर प्रत्येक कूद लक्ष्य, और एक कूद या वापसी के बाद प्रत्येक पता एकत्र करता है। प्रत्येक ब्लॉक को अपने अंतिम निर्देश से उत्तराधिकारी प्राप्त होते हैं। एक सशर्त कूद में दो उत्तराधिकारी होते हैं और एकjmpएक है।retकोई नहीं है, और एक ब्लॉक जो एक सामान्य निर्देश में समाप्त होता है अगले नेता के माध्यम से गिर जाता है।
totalपर-O1इसमें पांच ब्लॉक हैंः
| Block | Instructions | Successors |
|---|---|---|
0x40 | testl %esi, %esi then jle 0x5c | 0x5c, 0x44 |
0x44 | movl %esi, %ecx, xorl %edx, %edx, xorl %eax, %eax, nopw | 0x50 |
0x50 | addl (%rdi,%rdx,4), %eax, incq %rdx, cmpq %rdx, %rcx, jne 0x50 | 0x50, 0x5b |
0x5b | retq | exit |
0x5c | xorl %eax, %eax then retq | exit |
immediate_dominators()यह कूपर, हार्वे और केनेडी (2001) के पुनरावर्ती एल्गोरिथ्म है। यह विपरीत पोस्टऑर्डर में ब्लॉक चलता है और संसाधित पूर्ववर्ती के प्रभुत्वशाली को तब तक काटता है जब तक कि कुछ भी नहीं बदलता है। इस छोटे ग्राफ पर यह दो पास में समाप्त होता है।totalयह कहता है कि0x44प्रभुत्व रखता है0x50यह भी कहता है कि0x50पीछे के किनारे के माध्यम से खुद पर हावी है, जो कि चरण 8 लूप कैसे खोजता है।
एक ही कार्य दो बार अधिक चलाता है।dominance_frontiers()स्टेज 5 में एसएसए के लिए इसका इस्तेमाल करता है।post_dominators()इसे एक आभासी निकास नोड के साथ उल्टा ग्राफ पर चलाता है चरण 8 में जोड़ों को खोजने के लिए।
चरण 3: छोटे IR पर उठाना
आईआर में छह प्रकार के बयान हैंः Assign,Store,Branch,Jump,IndirectJumpऔर Return. अभिव्यक्ति निरंतर, चर, चौड़ाई वाले द्विआधारी संचालन, कास्ट (sext,zext,trunc), और भार। पांच और आकार मौजूद हैं केवल बाद में दूर करने के लिए सरलः Flags,Cond,Concat,SignFillऔर Symbolic. .
रजिस्टर उनके 64-बिट परिवार द्वारा नामित चर हैं।%eaxएक हैAssignraxचौड़ाई 32 के साथ, और एक रीडिंग के साथ %eaxraxचौड़ाई 32 पर। 64-बिट मोड में 32-बिट लिखने से पूर्ण रजिस्टर में शून्य-विस्तार होता है, इसलिए एक बाद में 64-बिट पढ़ने के समान मूल्य एक zext. लिफ्टर को पहुंच की परिभाषा नहीं पता है, इसलिए यह पढ़ने की चौड़ाई रिकॉर्ड करता है और एसएसए को चरण 5 में कास्ट डालने देता है।
| Instruction | IR statements |
|---|---|
subl %edi, %esi | flags = cmp(esi, edi) then esi = esi - edi |
testl %edi, %edi | flags = test(edi, edi) |
je 0xd | if je(flags) goto 0xd else goto 0x4 |
xorl %eax, %eax | eax = 0 |
cltd | edx = signfill(eax) |
idivl %edi | dividend = edx:eax, eax = dividend / edi, edx = dividend % edi |
movslq %edi, %rax | rax = sext64(edi) |
sarq $0x22, %rcx | rcx = rcx >>s 34 |
shrq $0x3f, %rax | rax = rax >>u 63 |
addl (%rdi,%rdx,4), %eax | flags = add(eax, load32(rdi + rdx * 4)) then eax = eax + load32(rdi + rdx * 4) |
leaq (%rip), %rcx with its relocation | rcx = &.rodata+0x14 |
pushq %rbp, movq %rsp, %rbp, subq $0x10, %rsp, popq %rbp | nothing, frame bookkeeping |
retq | return rax |
ध्वज एक चर हैं
प्रत्येक निर्देश जो स्थिति कोड सेट करता है एक चर नामित लिखता है flags, और हर सशर्त कूद इसे पढ़ता है.cmp a, bलिखता है cmp(a, b), ए test a, bलिखता है test(a, b)और subवही लिखता है cmpएक साइड इफेक्ट के रूप में आकार।jccबन जाता हैBranch(Cond(cc, flags), target, fallthrough). स्थिति कोड तुलना ऑपरेटर का निर्णय करता है जब दोनों चरण 6 में मिलते हैंः
| Code | After cmp(a, b) | After test(a, a) |
|---|---|---|
je | a == b | a == 0 |
jne | a != b | a != 0 |
jl, jge, jle, jg | signed <, >=, <=, > | signed compare with 0 |
jb, jae, jbe, ja | unsigned <, >=, <=, > | unsigned compare with 0 |
xorl %eax, %eaxसीधे ऊपर उठाया जाता है eax = 0. यह ध्वज यह सेट करता है लिखा है के रूप मेंaddशैली ध्वज जो कोई भी जंप में फिक्स्चर पढ़ता है।xorभाषा में मायने रखता है grade, जहां गिदरा खो गया।
सीटीडी और आईडीवी
cltdभरें%edx के संकेत के साथ%eax. .idivl %edi64-बिट जोड़ी को विभाजित करता है %edx:%eaxद्वारा %edi, गुणांक को जोड़ता है %eax, और शेष %edxलिफ्टर एक अस्थायी नाम में जोड़े लिखता हैdividendपहले। शेष कथन फिर पुराने पढ़ता है%eaxएसएसए में उच्च आधा दिखाई देता हैsignfillसरलता जोड़ी को कम आधा तक कम करता है, और विभाजन एक 32-बिट हो जाता है /. .
-O0 पर स्टैक स्लॉट
-O0क्लिंग फ्रेम में हर स्थानीय रखता है।percent_changeदुकानें %edi-0x8(%rbp)और %esi-0xc(%rbp)और फिर हर उपयोग के लिए उन्हें वापस पढ़ता है। लिफ्टर के लिए देख रहा हैpushq %rbpइसके बाद movq %rsp, %rbp. उस प्रोग्रू के बाद, एक स्मृति ओपेरा साथ आधार%rbp, कोई सूचकांक नहीं है, और एक विस्थापन एक चर नामित हो जाता है local_<hex>. .-0x8(%rbp)है local_8,-0xc(%rbp)है local_c. .
फ्रेम निर्देश स्वयं कोई आईआर नहीं उत्पन्न करते हैं, और न ही करते हैं subqऔर addqपर%rsp. किसी भी अन्य स्मृति ओपेरा एक रहता है .Loadया Store. .
स्थानांतरण
leaq (%rip), %rcxमें gradeफ़ाइल में विस्थापन शून्य है।0x36कहते हैंR_X86_64_PC32.rodata+0x10. लिंकर लिखेंगे S + A - Pमैदान में।S का पता है.rodata,Aजोड़ा है 0x10और Pइस प्रकार, इस क्षेत्र का पता है।next_ip + disp = (P + 4) + S + A - P = S + A + 4. .
लिफ्टर एक ही अंकगणित को बिना लिंकर के लागू करता है। लक्ष्य ऑफसेट अतिरिक्त है और निर्देश के अंत तक क्षेत्र से दूरी, 0x10 + 4 = 0x14. आईआर कहते हैंrcx = &.rodata+0x14, और वहाँ की मेज पांच रखता है intमान 1, 2, 3, 4, 4. .
चरण 4: आमंत्रण सम्मेलन से पैरामीटर
सिस्टम V AMD64 psABI, धारा 3.2.3, INTEGER वर्ग तर्क में पारित %rdi,%rsi,%rdx,%rcx,%r8,%r9, उस क्रम में। परिणाम वापस आता है %raxऔर फिर %rdx. धारा 3.2.1 में कहा गया है कि %rbp,%rbxऔर %r12पार%r15यह सभी कॉल करने वाले के लिए हैं और उन्हें संरक्षित किया जाना चाहिए।
collect_params()एक नियम लागू होता हैः फ़ंक्शन में किसी भी लेखन से पहले पढ़ा जाता है जो तर्क रजिस्टर एक पैरामीटर है। इस तरह के उच्चतम रजिस्टर गिनती को तय करता है, इसलिए एक फ़ंक्शन जो पढ़ता है %rsiऔर कभी नहीं%rdiअभी भी दो मापदंडों है।percent_changeपढ़ता है %ediऔर %esi, तो यह है param_1और param_2. .gradeकेवल पढ़ता है %edi. .totalपढ़ता है %rdiएक 64-बिट आधार के रूप में और %esiएक 32-बिट गिनती के रूप में।
अब गिदरा की param_3एक स्पष्टीकरण है।idivlलिखता है %rdx, और psABI एक 128-बिट INTEGER रिटर्न में अनुमति देता है %rax:%rdx. वस्तु फ़ाइल में कोई कॉल करने वाला नहीं है कि रिटर्न मान कितना व्यापक है. गिड्रा ने रिटर्न मान के रूप में दोनों रजिस्टर बनाए रखे।
शून्य पथ पर,xorl %eax, %eaxकेवल लिखता है %eax. गिदरा पढ़िये .%rdxवहाँ एक समारोह के इनपुट के रूप में, और उस इनपुट बन गया param_3निर्देश सही हैं. हस्ताक्षर निर्देशों के खुले छोड़ भागों के बारे में एक अनुमान है.
चरण 5: एसएसए, परिभाषा प्रति एक नाम
उठाने के बाद, gradeलिखता है raxचार बार और rcxतीन बार. साइट्रॉन, फेर्रेन्ट, रोसेन, वेगमैन और ज़ेडेक (1991) स्थैतिक एकल असाइनमेंट फॉर्म को परिभाषित करते हैं। प्रत्येक चर को एक बार ही सौंपा जाता है, और जोड़ बिंदुओं पर फ़ी फ़ंक्शन उन परिभाषाओं में से चुनते हैं जो उन्हें प्राप्त करते हैं। इनका निर्माण दो चरणों में होता है। धारा 5.1 में प्रभुत्व सीमाओं का उपयोग करके फि फ़ंक्शन को रखा गया है, और धारा 5.2 में प्रभुत्व वृक्ष के ऊपर चलने के साथ प्रत्येक परिभाषा और उपयोग का नाम बदल दिया गया है।insert_phis()और rename()उन दो खण्डों का पालन करें।
एसएसए सूची totalयह दिखाता है कि निर्माण क्या उत्पन्न करता हैः
textblock 0x44 -> 0x50
ecx_1 = esi_0
edx_1 = 0
eax_1 = 0
block 0x50 -> 0x50, 0x5b
eax_2 = phi(0x44: eax_1, 0x50: eax_3)
rdx_2 = phi(0x44: edx_1, 0x50: rdx_3)
flags_2 = phi(0x44: flags_1, 0x50: flags_5)
flags_3 = add(eax_2, load32(rdi_0 + rdx_2 * 4))
eax_3 = eax_2 + load32(rdi_0 + rdx_2 * 4)
flags_4 = inc(rdx_2, 1)
rdx_3 = rdx_2 + 1
flags_5 = cmp(zext64(ecx_1), rdx_3)
if jne(flags_5) goto 0x50 else goto 0x5bकिसी रजिस्टर का संस्करण 0 उसके प्रविष्टि पर उसका मान है। esi_0और rdi_0वे पैरामीटर हैं. लूप हेडर 0x50दो पूर्ववर्ती है, सेटअप ब्लॉक और खुद, तो लूप में एक परिभाषा के साथ प्रत्येक चर वहाँ एक phi मिलता है।flags_2पीआई आगमन पर मृत है. न्यूनतम एसएसए इसे डालता है क्योंकिflagsदोनों पूर्ववर्ती में परिभाषाएं हैं, और चरण 6 इसे हटा देता है क्योंकि कुछ भी इसे पढ़ता नहीं है।
नाम बदलने का कदम चौड़ाई को भी ठीक करता है। edx_1 = 0एक 32-बिट परिभाषा है और rdx_3 = rdx_2 + 1है 64-बिट, तो phi rdx_2यह व्यापकता लेता है।%rcxमें cmpq %rdx, %rcxहै 64-बिट जबकि इसकी परिभाषा ecx_1 = esi_0है 32-बिट, तो उपयोग हो जाता है zext64(ecx_1)अंतिम छद्म कोड में प्रत्येक कास्ट एक नियम से आता है।
"लाइट" भाग एसएसए से बाहर निकलने का रास्ता है।coalesce_webs()यह प्रत्येक फि को अपने तर्कों के साथ एक चर में जोड़ता है, जिसे वेब कहा जाता है, और वेब को एक नाम देता है। यह उन आकारों के लिए काम करता है जो एक संकलक इन कार्यों के लिए उत्सर्जित करता है। यह समानांतर प्रतियों पर टूट सकता है, जहां एक पर दो फि एक्सचेंज मानों को जोड़ते हैं, और पाठ उस मामले को संभाल नहींता है। साइट्रॉन की धारा 7 रंग के माध्यम से सामान्य आउटपुट का वर्णन करती है।
चरण 6: सरलीकृत करें, प्रतिस्थापित करें, मृत कोड हटाएं
optimize()सरल करने वाला इन नियमों के साथ नीचे से ऊपर के अभिव्यक्ति को फिर से लिखता हैः
| Before | After |
|---|---|
Cond(je, cmp(a, b)) | a == b, and the same for the other nine codes |
Cond(jle, test(a, a)) | a <= 0 |
x + (-6) | x - 6 |
trunc32(zext64(x)) where x is 32-bit | x |
trunc32(a + b) on 64-bit operands | trunc32(a) + trunc32(b) |
(signfill(x): x) / d | x / d at width 32 |
load32(base + idx * 4) | base[idx] |
trunc32(p >>s 34) + trunc32(p >>u 63) with p = sext64(x) * 0x66666667 | x / 10 |
आगे प्रतिस्थापन एक परिभाषा को उसके एकमात्र उपयोग में ले जाता है। नियम में तीन शर्तें हैं। उपयोग एक ही ब्लॉक में है। उपयोग एक कथन है और एक phi नहीं। जब परिभाषा में लोड होता है, तो कोई स्टोर या कॉल परिभाषा और उपयोग के बीच नहीं बैठता है। एक स्थिर या एक अनुभाग पता को किसी भी स्थान पर प्रतिस्थापित किया जाता है, सिवाय phi के, क्योंकि एक phi तर्क को वेब के लिए एक चर रहना होगा।
मृत कोड हटाने से एक असाइनमेंट या एक phi को हटा दिया जाता है जिसका गंतव्य का कोई उपयोग नहीं होता है और जिसका अभिव्यक्ति का कोई कॉल नहीं होता है। पास के क्रम में मायने रखता है।percent_changeशेष edx_2 = dividend_1 % edi_0यह पहले चला जाता है, तो यह छोड़ देता हैedx_1 = signfill(eax_1)एक बार इस्तेमाल करने के लिए, इसलिए इसे dividend_1 = edx_1:eax_1. .
जोड़े नियम फिर कम करता है signfill(eax_1):eax_1eax_1, और विभाजन बन जाता है eax_1 / edi_0. अब .eax_1 = esi_1 * 100एक प्रयोग है, और esi_1 = esi_0 - edi_0एक उपयोग है, और दोनों वापसी बयान में स्थानांतरित कर दिया।flagsपरिभाषाओं को शाखा की शर्तों में बदल दिया गया है और गायब हो जाते हैं। चार निर्देश एक पंक्ति बन जाते हैंः
creturn (param_2 - param_1) * 100 / param_1;0x66666667 गुणा
gradeस्कोर को 10 से विभाजित करता है, और क्लेंग नहीं देता है idiv. यह उत्सर्जन करता है .imulq $0x66666667, %rax, %rax, तो sarq $0x22, %rcxएक प्रति पर, तो shrq $0x3f, %raxग्रैनलंड और मोंटगोरी, "विभाजन द्वारा अपरिवर्तित पूर्णांक गुणा का उपयोग करके" (PLDI 1994), विधि का वर्णन करते हैं। संकलक एक गुणक चुनता है mऔर एक शिफ्ट s. . हर एक के लिए .xसीमा में, (x * m) >> sबराबर होना चाहिएx / d. .
यहाँm = 0x66666667 = 1717986919और s = 34अंकगणित सत्यापित करता हैः2^34 / 10 = 1717986918.4, और छत है 1717986919. . गलती हैm * 10 - 2^34 = 6. पहचान को हर 32-बिट पर रखने के लिए .x, त्रुटि समय 2^31नीचे रहना चाहिए 2^34, तो त्रुटि अधिकतम 8 होना चाहिए, 6 अधिकतम 8 है, तो (x * m) >> 34बराबर floor(x / 10)प्रत्येक गैर-नकारात्मक के लिए int. .
नकारात्मक के लिए xतल शून्य से दूर घूमता है जबकि सी शून्य की ओर घूमता है। shrq $0x3fउत्पाद के संकेत बिट को निकालता है, जो 1 है ठीक जब xयह जोड़ने के लिए गोल करने को ठीक करता है।
magic_divisor(m, s)यह उम्मीदवार जोड़े का परीक्षण करता है. यह गणना करता हैd के पूर्णांक गुणांक के रूप में2^sद्वारा mऔर कोशिश करता है dऔर d + 1. यह एक स्वीकार करता है dकेवल जब m की सीमा के बराबर है2^s / dऔर त्रुटि बंधन बरकरार है.s = 33देता हैd = 5, जो सही है, क्योंकि 0x66666667यह भी छत है 2^33 / 5. एक परीक्षण पहचान की जाँच करता हैc_div(x, 10) intरेंज।
मान्यता के साथ,gradeपढ़ता है ecx_1 = param_1 / 10 - 6;पहचान बंद के साथ, डेमो कच्चे रूप प्रिंट करता हैः
crax_1 = (long)param_1 * 0x66666667;
ecx_1 = (int)(rax_1 >> 34) + (int)((unsigned long)rax_1 >> 63) - 6;जब आप किसी ऐसे कम्पिलर से कोड पढ़ते हैं जिसका आपने अध्ययन नहीं किया है, तो कच्चे रूप ईमानदार होता है।
चरण 7: उपयोग के प्रकार
मशीन कोड में चौड़ाई है और कोई प्रकार नहीं है।infer_types()एक वेब जिसकी परिभाषाएं 32 बिट्स चौड़ी हैंint, और एक 64-बिट वेब है long. एक वेब जो तत्व पहुँच का आधार है base[idx]4 बाइट तत्वों के साथ int *. पॉइंटर प्रकार प्रतियों और फिस के साथ फैलता है, तो local_8 = param_1पर-O0दोनों को बनाता है int *. रिटर्न प्रकार में मूल्य की चौड़ाई हैreturnबयान।
परिणाम totalहै int total(int *param_1, int param_2). . गिदरा ने लिखा था .int total(long param_1,uint param_2). दोनों निर्देशों के अनुरूप हैं. लिफ्टर ने पैमाना भार का उपयोग संकेतक के रूप में किया। गिदरा ने रजिस्टर की चौड़ाई रखी और प्रवेश को लिखा कि*(int *)(param_1 + uVar2 * 4). . . और न ही देख सकते हैं .constऔर न तो यह पता हैint *सेunsigned *. .
ली, एव्गेरिनोस और ब्रमले (टीआईई, एनडीएसएस 2011) फ्रेम प्रकार की वसूली एक ग्रिड पर बाधा समाधान के रूप में। परिणाम एक अनुमान के बजाय एक ऊपरी और निचली सीमा है। नोनन, लॉगिनॉव और कोक (Retypd, PLDI 2016) बहुरूपवाद और पुनरावृत्ति जोड़ते हैं। वे 98 प्रतिशत याद करने की रिपोर्ट करते हैं।constइस पाठ में उस काम की रेखा से दो सबसे सस्ते नियम का उपयोग किया जाता है और वे जो नहीं देख सकते हैं उसे नाम देते हैं।
असीमित अंकगणित को अभिव्यक्ति में रखा जाता है, चर प्रकारों में नहीं। jaके बादcmpl $0x4, %ecx(unsigned int)ecx_1 > 4और shrq(unsigned long)rax_1 >> 63. चर हस्ताक्षरित रहते हैं ताकि पाठक रजिस्टर मान और एक स्थान को देखे जहां निर्देश इसे हस्ताक्षरित नहीं माना जाता है।
चरण 8: ग्राफ से संरचना
सिफुएंट्स (1994), अध्याय 6, ग्राफ के क्षेत्रों को उच्च स्तरीय भाषा के आकारों के साथ मेल खाता है। लूप पहले (6.6.1) आते हैं, फिर दोतरफा कंडीशन्स (6.6.2) आते हैं, फिर nतरफा कंडीशन्स (6.6.3) । यकडन, एस्च्वेलर, गेर्हार्ड्स-पैडिला और स्मिथ (NDSS 2015) दूसरे रास्ते पर जाते हैं। वे उस स्थिति से उत्पन्न होते हैं जिसके तहत प्रत्येक नोड निष्पादित करता है और बिना किसी निश्चित आकार के उस स्थिति से संरचना का निर्माण करता है। उनका प्रेरणा एक माप है। एक नमूना पर हेक्स-रेज 1571goto49 514 पंक्तियों में एक प्रति 32 पंक्तियों में बयान, और उनके संरचना 1.530 कोर्यूटिल कार्यों पर कोई उत्पादन नहीं किया।
structure()यह एक ब्लॉक के तत्काल पोस्ट-डोमिनटर है जो एक में समाप्त होता है की दो तथ्यों की आवश्यकता होती है।Branchएक बैक एज एक ब्लॉक का एक किनारा है जो इसके स्रोत पर हावी है। प्राकृतिक लूप ब्लॉक का सेट है जो हेडर को पारित किए बिना बैक एज स्रोत तक पहुंचता है।
एक के लिए Branchजोड़ के साथ j:
- यदि
jकूद लक्ष्य है, उत्सर्जनif (!cond) { fallthrough region }और आगे बढ़ेंj. . - यदि
jहै गिरावट, उत्सर्जनif (cond) { target region }और आगे बढ़ेंj. . - यदि
jफंक्शन आउटपुट है, दोनों हाथों वापस.if (cond) {... }और उसके बाद दूसरी बांह, बिना किसीelseयह प्रारंभिक वापसी हैpercent_changeऔरtotal. . - अन्यथा उत्सर्जन
if (cond) { target } else { fallthrough }और आगे बढ़ेंj. . यह हैpercent_changeपर-O0, जहां दोनों हथियार भंडारणlocal_4और हम एक उपनिवेश में मिलेंगे।
लूप हेडर के लिएः
- यदि शीर्षक एक में समाप्त होता है
Branchएक उत्तराधिकारी के साथ लूप के बाहर, emitwhile (cond) { body }. .totalपर-O0इस तरह का हैः हेडर तुलना करता हैlocal_14के साथlocal_cऔर पत्ते परjge. . - यदि हेडर भी पीछे के किनारे के साथ ब्लॉक है, या इसके बजाय लॉच आउटपुट, emit
do { body } while (cond);. .totalपर-O1इस आकार का हैः ब्लॉक0x50जोड़ता है, वृद्धि, तुलना करता है, और खुद के लिए कूदता है।
एक IndirectJumpबिना उत्तराधिकारी के एक ब्लॉक समाप्त होता है।gradeपर-O0एक असली कूद टेबल है, jmpq *%rax.rodata+0x0और पाठ 33 उन लक्ष्यों को हल करता है। यहाँ लिफ्टर एक नोट प्रिंट करता है इसके बजाय. चार ब्लॉक पर0x76,0x7f,0x88और 0x91अभ्यास प्रयोगशाला कहते हैं कि यह केवल अप्रत्यक्ष कूद के माध्यम से प्राप्त किया गया था और उठाया नहीं गया था।switch. .
-ओ1 ने स्विच से क्या किया
-O1क्लिंग के लिए एक कूद टेबल नहीं रखा grade. यह गणना की गई थी .score / 10 - 6और परिणाम की तुलना 4 के साथ एक असीमित संख्या के रूप में किया। फिर यह लोड किया रिटर्न मान से पांच प्रविष्टि तालिका से intपर.rodata+0x14. असीमित तुलना एक निर्देश है जो दोनों को कवर करता है score / 10 < 6और score / 10 > 10एक नकारात्मक अंतर एक बड़ा अनसीन मूल्य है, इसलिए यह एक ही चेक में विफल रहता है।
टेबल पकड़ता है 1, 2, 3, 4, 46 से 10 तक के गुणांक के लिए।switchपांच के साथ returnबयानों में एक ifलिफ्टर ठीक यही उत्सर्जित करता है, और टेबल का नाम देता हैlookup_14उसके खंड के बाद।
तालिका की लंबाई एक हेरिस्टिक है।table_values()तालिका से शुरू करने के लिए अगले से पढ़ता है .rodataकिसी भी की तुलना.textइस वस्तु में अगला संदर्भित ऑफसेट 0x28, तालिका grade_label, तो lookup_14है(0x28 - 0x14) / 4 = 5अघोषित तुलना 4 के साथ सहमत है. गिदरा लंबाई भी नहीं जानता. यह लिखा था*(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4), एक बाइट पता प्लस एक ऑफसेट, और पाठक के लिए देखने के लिए पांच मानों छोड़ दिया.
गिदरा के साथ
डेमो हर जोड़ी प्रिंट करता है. Ghidra पाठ से आता हैcode/fixtures/ghidra/*.json, सबूत रिकॉर्ड कि rea 2026-10-14 पर लौटा।npx -y rea-agents@6.5.0 decompile ledger-x86_64-O1.o <function> --provider ghidra --format json. प्रत्येक रिकॉर्ड में है confidence: observedऔर authority: shipped-artifact. पाठ का पालन किया जाता है. पाठ के अंदर के दावे व्युत्पन्न होते हैं, और रिकॉर्ड में नहीं कहा जाता कि कौन से हैं.
प्रतिशत_बदला
textlifter | ghidra
-----------------------------------------------+-----------------------------------------------
int percent_change(int param_1, int param_2) | undefined1 [16] percent_change(int param_1,int param_2,ulong param_3)
{ | {
if (param_1 == 0) { | long lVar1;
return 0; | undefined1 auVar2 [16];
} | undefined1 auVar3 [16];
return (param_2 - param_1) * 100 / param_1; | if (param_1 != 0) {
} | lVar1 = (long)((param_2 - param_1) * 100);
| auVar3._0_8_ = lVar1 / (long)param_1 & 0xffffffff;
| auVar3._8_8_ = lVar1 % (long)param_1 & 0xffffffff;
| return auVar3;
| }
| auVar2._8_8_ = 0;
| auVar2._0_8_ = param_3;
| return auVar2 << 0x40;
| }| Difference | Instruction or guess |
|---|---|
Return type undefined1 [16] against int | Guess. idivl writes %rax and %rdx, and no caller fixes the return width. Both registers were kept. |
param_3 | Guess that follows from the first. On the zero path only %eax is written, so %rdx is read as an input. |
lVar1 / (long)param_1 & 0xffffffff against / param_1 | Instruction. cltd plus idivl is a 64-bit division of a sign-extended 32-bit value, truncated to 32 bits. Both texts describe it. Ours reduced the pair with the signfill rule. |
lVar1 % (long)param_1 kept | Follows from the return type. The remainder in %rdx is dead in our analysis and alive in Ghidra's. |
if (param_1 != 0) with the zero case after it | Instruction order. je 0xd jumps to the zero return. Ghidra put the computed arm first, we put the smaller arm first. Both are correct. |
return auVar2 << 0x40 | Guess. A 16-byte value with param_3 in the low half, shifted into the high half, is how the decompiler expresses %rax = 0 with %rdx unknown. |
में मरम्मत संस्करणcode/fixtures/ghidra/ledger-x86_64-O1.ghidra-repaired.cकेवल इस फ़ंक्शन को संपादित करता है, और परीक्षण 81 इनपुट जोड़े पर स्रोत के साथ तुलना करता है।
ग्रेड
textlifter | ghidra
-----------------------------------------------+-----------------------------------------------
static const int lookup_14[5] = {1, 2, 3, 4, 4}; | uint grade(int param_1)
int grade(int param_1) | {
{ | uint uVar1;
int eax_1; | uint uVar2;
int ecx_1; |
| uVar2 = param_1 / 10 - 6;
ecx_1 = param_1 / 10 - 6; | uVar1 = -(param_1 >> 0x1f) ^ (uint)((ulong)((long)param_1 * 0x66666667) >> 0x3f);
eax_1 = 0; | if (uVar2 < 5) {
if ((unsigned int)ecx_1 <= 4) { | uVar1 = *(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4);
eax_1 = lookup_14[(unsigned long)(unsigned int)ecx_1]; | }
} | return uVar1;
return eax_1; | }
} || Difference | Instruction or guess |
|---|---|
param_1 / 10 - 6 in both | Instruction. Both recognized the 0x66666667 idiom. |
uVar1 = -(param_1 >> 0x1f) ^ (uint)(... >> 0x3f) against eax_1 = 0 | Instruction xorl %eax, %eax at grade+0x1a. The register is zero. Ghidra expressed the xor as the sign of param_1 exclusive-or the sign bit of the product, two spellings of the same bit, and did not cancel them. The value is 0 for every int, and the test proves it over 141 scores. A reader who trusts the text believes the default depends on the score. |
uVar2 < 5 against (unsigned int)ecx_1 <= 4 | Instruction cmpl $0x4, %ecx then ja. Same condition. Ghidra typed uVar2 as uint, we cast at the use. |
*(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4) against lookup_14[...] | Instruction plus relocation. Ghidra loaded the object with .rodata at 0x001000a0, so 0x001000b4 is .rodata + 0x14. It applied the relocation and kept a byte address. We named the table and read its five values. |
uint return against int | Guess on both sides. The instructions only say 32 bits. |
कुल
textlifter | ghidra
-----------------------------------------------+-----------------------------------------------
int total(int *param_1, int param_2) | int total(long param_1,uint param_2)
{ | {
int eax_1; | int iVar1;
int ecx_1; | ulong uVar2;
long rdx_1; |
| if (0 < (int)param_2) {
if (param_2 <= 0) { | uVar2 = 0;
return 0; | iVar1 = 0;
} | do {
ecx_1 = param_2; | iVar1 = iVar1 + *(int *)(param_1 + uVar2 * 4);
rdx_1 = 0; | uVar2 = uVar2 + 1;
eax_1 = 0; | } while (param_2 != uVar2);
do { | return iVar1;
eax_1 = eax_1 + param_1[rdx_1]; | }
rdx_1 = rdx_1 + 1; | return 0;
} while ((unsigned long)(unsigned int)ecx_1 != rdx_1); | }
return eax_1; |
} || Difference | Instruction or guess |
|---|---|
int *param_1 against long param_1 | Inference on our side from addl (%rdi,%rdx,4), %eax. Ghidra kept the width. |
uint param_2 and (int)param_2 against int param_2 | Ghidra chose unsigned because cmpq %rdx, %rcx compares the zero-extended count. We kept int and cast at the compare. |
do { } while in both | Instruction jne 0x50 from the block to itself. The compiler rotated the loop. The -O0 object keeps the while. |
ecx_1 = param_2 kept against param_2 != uVar2 | Our substitution rule stays inside one block. The copy movl %esi, %ecx is in the setup block and the compare is in the loop, so the variable stays. Ghidra propagated it. |
(unsigned long)(unsigned int)ecx_1 | Instruction. movl %esi, %ecx zero-extends, and the 64-bit compare reads the full register. |
व्यवहार परीक्षण Ghidra पाठ को संकलित करता है gradeऔर totalअपरिवर्तित. यह दो जोड़ता हैtypedefरेखाओं और परिभाषा DAT_001000b4 पर बीस बाइट से.rodata+0x14. दोनों कार्य ग्रिड पर स्रोत से मेल खाते हैं.
Pseudocode पढ़ने के बारे में क्या कहते हैं शोध
एलएलएम4डेकंपिल (ईएमएनएलपी 2024) ने मानव-एवल-डेकंपिल पर गिद्रा को 20.12 प्रतिशत पुनः निष्पादन क्षमता पर मापा। लगभग पांच में से एक फ़ंक्शन ने बेंचमार्क के साथ आने वाले दावों को फिर से संकलित किया और पारित किया। 6.7B मॉडल के साथ गिड्रा के उत्पादन को परिष्कृत करने से यह 52.74 प्रतिशत तक पहुंच गया। डीकंपिलबेंच (एसीएल 2025 की खोजें) ने 130 कार्यक्रमों से 23,400 कार्यों पर छह औद्योगिक डीकंपिलर और छह एलएलएम दृष्टिकोण चलाए। LLM आउटपुट बेहतर पढ़ते थे और 52.2 प्रतिशत कम कार्यक्षमता सटीकता थी।
लियू, राफ और मिकिन्स्की (arXiv 2609.05370, सितंबर 2026) अंतर की व्याख्या करते हैं। उन्होंने 300 GitHub पुस्तकालय कार्यों और 287 CVE-आधारित कार्यों का उपयोग किया। सबसे मजबूत परिष्करण मॉडल ने गिद्रा की निर्माण दर को 75 से बढ़ाकर 90 प्रतिशत कर दिया जबकि व्यवहारिक मैच दर 74 से घटकर 62 प्रतिशत कर दी। बेंचमार्क में प्रत्येक परीक्षा में उत्तीर्ण उम्मीदवारों ने अभी भी फ्यूज इनपुट पर अंतर किया। यह आठ प्रणालियों में 4.9 प्रतिशत मामलों में हुआ, और एक प्रणाली के लिए 13 प्रतिशत तक। खुलासा किए गए कमजोरियों का एक-दसवां हिस्सा तक परिष्कृत कोड से गायब हो गया।
वे इसका कारण सरल है। इनका आविष्कार किए गए फ़ील्ड, टाइप, कॉल और गार्ड ने उन अज्ञात दृश्यों की जगह ली जो डिकोम्पाइलर ने पाठ में छोड़ दी थीं।
यह कारण है कि लिफ्टर प्रिंट करता है (unsigned long)(unsigned int)ecx_1यह एक नोट के बजाय एक प्रिंट करता हैswitchअनसुलझे कूद के लिए, और lookup_14प्रत्येक पंक्ति में एक व्युत्पन्न है आप पढ़ सकते हैं मेंcode/main.py. . . गिदरा की undefined1 [16]एक परिष्कृत मॉडल साफ है।int percent_change(int, int)एक ही मॉडल अगले समारोह पर एक साफ गलत हस्ताक्षर उत्पन्न करेगा।
इसे बनाओ
डेमो चलाएँः
bashcd phases/13-tools-and-protocols/34-lifting-to-pseudocode
python3 code/main.pyडेमो सभी चार कार्यों को डिकोड करता है -O1उद्देश्य और प्रतिबद्ध के साथ प्रत्येक निर्देश की तुलना करता है llvm-objdumpपाठ के लिए।percent_change,gradeऔर totalयह एसएसए से पहले आईआर, पीआई कार्यों के साथ एसएसए सूची और गिड्रा पाठ के बगल में छपाई छपाई। फिर यह प्रिंट करता है gradeविभाजन पहचान बंद कर दिया गया है, -O0 के संस्करणpercent_change,totalऔर grade_label, और एक सारांशः
json{
"functions": ["grade", "grade_label", "percent_change", "total"],
"instructions": 56,
"blocks": {"percent_change": 3, "grade": 3, "total": 5, "grade_label": 4},
"phis": {"percent_change": 0, "grade": 1, "total": 2, "grade_label": 1},
"objdumpDifferences": 0
}पढ़िए code/main.pyइस क्रम मेंः
read_object()अनुभाग के शीर्षक, प्रतीक तालिका और.relaELF64 स्थानांतरित करने योग्य वस्तु के खंड।decode_one()औरrender_att()एक निर्देश को डिकोड और इसे इस तरह से प्रिंटllvm-objdumpहै।build_blocks(),immediate_dominators()औरdominance_frontiers()ग्राफ तथ्यों का निर्माण करें।lift_insn()एक निर्देश को आईआर कथन में बदल देता है और स्थानांतरण को हल करता है।insert_phis()औरrename()SSA का निर्माण करें और चौड़ाई कास्ट डालें।make_simplifier(),substitute_once()औरremove_dead_code()अंदर भागेंoptimize(). .collect_params(),coalesce_webs()औरinfer_types()नाम और वेब टाइप करें।structure()के साथpost_dominators()औरnatural_loops()सी शरीर को उत्सर्जित करता है।lift_function()चरणों को एक साथ जोड़ता है औरlift_all()कॉल के हस्ताक्षर के साथ एक पूरे वस्तु को उठाता है।
इसका प्रयोग करें
अपने स्वयं के वस्तु से एक समारोह को उठाने के लिए, इसे निर्माण के साथ clang -target x86_64-unknown-linux-gnu -cऔर कॉलlift_all(read_object(path)). जब डिकोडर उठता है DecodeError, संदेश से ऑप्टकोड बाइट पढ़ें और इसे SDM ऑप्टकोड मानचित्र में खोजें। फिर फ़ाइल के शीर्ष पर तालिकाओं में एक प्रविष्टि जोड़ें। जब लिफ्टर एक mnemonic पर उठाता है, तो एक नियम जोड़ें lift_insn(). जब संरचनाकार एक goto, के साथ ग्राफ प्रिंट करें IrPrinterऔर निर्णय लें कि किस आकार नियम में कमी है। प्रत्येक जोड़ छोटा है और प्रत्येक के खिलाफ जाँच की जाती हैllvm-objdumpया मौजूदा परीक्षणों से क्लिंग।
जब गिड्रा स्थापित हो जाए, तो एक ही फ़ंक्शन पर rea चलाएं और दोनों पाठों को एक साथ रखें। किसी भी पाठ पर विश्वास करने से पहले अंतर तालिका भरें। इसके पीछे एक निर्देश के साथ एक अंतर एक प्रस्तुति विकल्प है। इसके पीछे कोई निर्देश नहीं होने वाला अंतर एक अनुमान है, और अनुमान दोनों पक्षों पर हो सकता है।
इंटरैक्टिव लैब
लैब ए: पांच चरणों के माध्यम से एक कार्य
codeनिर्देशिका, खुला पायथनः
bashcd phases/13-tools-and-protocols/34-lifting-to-pseudocode/code
python3 -qदौड़ें:
pythonfrom main import *
obj = read_object(FIXTURES / "ledger-x86_64-O1.o")
lifted = lift_function(obj, "percent_change")
for insn in lifted.decoded: print(f"0x{insn.address:x}: {insn.raw.hex(' ')} {insn.att()}")
print(lifted.ir_text)
print(lifted.ssa_text)
print(lifted.pseudocode)अनुसरण करें%esiसेsubl %edi, %esiesi_1 = esi_0 - edi_0(param_2 - param_1). बाइट fe के ModR/M हैsubl, के साथ mod = 11,reg = ediऔर r/m = esi. .
लैब बीः विभाजन की भाषा
pythonprint(lift_function(obj, "grade", recognize_division=False).pseudocode)
print(magic_divisor(0x66666667, 34), magic_divisor(0x66666667, 33), magic_divisor(0x66666667, 31))पहली पंक्ति कच्चे गुणा और शिफ्ट प्रिंट करती है। दूसरी छपाई।10 5 None. पता लगाएं कि एक ही गुणक को 5 से विभाजित क्यों किया जाता है, एक शिफ्ट कम के साथ, और क्यों शिफ्ट 31 त्रुटि सीमा को विफल करता है।
लैब सीः एक नई वस्तु डिकोडर को रोकती है
पर फिटिंग का निर्माण करें-O2और उसे उठाओः
bashclang -target x86_64-unknown-linux-gnu -O2 -fno-asynchronous-unwind-tables -c fixtures/ledger.c -o "$TMPDIR/ledger-O2.o"
python3 -c 'from main import *; from pathlib import Path; import os; lift_all(read_object(Path(os.environ["TMPDIR"]) / "ledger-O2.o"))'डिकोडर पर रुकता है 0x56के साथopcode 81 is outside the fixture subset. Opcode 8132 बिट तत्काल के साथ समूह 1 फॉर्म है।andl $0x7ffffff8, %edx, और क्लेंग ने इसे वेक्टरिज़ेड लूप में इस्तेमाल किया total. इसे इसके पास जोड़ें 0x83में decode_one()उपसमूह एक प्रविष्टि में एक बार में बढ़ता है, और प्रत्येक प्रविष्टि के खिलाफ जाँच की जाती हैllvm-objdump. .
लैब डीः ग्रेड में गिद्रा का डिफ़ॉल्ट मान
pythonprint(ghidra_pseudocode("grade"))
for score in (-25, 0, 59, 60, 100, 115):
sign = -(score >> 31)
product_sign = ((score * 0x66666667) & ((1 << 64) - 1)) >> 63
print(score, sign ^ product_sign)हर पंक्ति में 0 छपा है।uVar1अभिव्यक्ति सही और अपठनीय है, और निर्देश xorl %eax, %eaxयह दो बाइट में कहता है।
अभ्यास प्रयोगशाला
जोड़ें switchgradeपर-O0. .
आवश्यकताएँः
- में पांच स्थानान्तरणों को पढ़ें
.rela.rodataledger-x86_64-O0.o, प्रत्येकR_X86_64_PC32.text+offset, और तालिका से पांच मामले के लक्ष्य की गणना करें.rodata+0x0. . - एक जोड़ें
Switchआईआर के लिए समापनकर्ता सूचकांक अभिव्यक्ति, लक्ष्य सूची और डिफ़ॉल्ट सेjaटेबल लोड से पहले। - स्विच ब्लॉक को प्रति लक्ष्य एक उत्तराधिकारी दें ताकि डोमिनटर्स, पोस्ट डोमिनटर्स और एसएसए काम करते रहें।
- जारी करें
switch (local_10) { case 0:... }एक के साथcaseप्रति लक्ष्य और संयुक्त के बाद साझा उपनाम। - किसी भी अप्रत्यक्ष कूद के लिए नोट रखें जिसका टेबल आप नहीं बांध सकते हैं।
- क्लिंग के साथ परिणाम को संकलित करें और परीक्षणों में स्कोर ग्रिड पर स्रोत के साथ इसकी तुलना करें।
सफलता का अर्थ हैgradeपर-O0और gradeपर-O1एक के साथ नेटवर्क पर एक ही आउटपुट उत्पन्न करते हैं switchऔर नहींgoto. .
कलाकृतियाँ
यह सबक हमें फल देता हैoutputs/skill-lifting-to-pseudocode.md. इसका उपयोग प्रत्येक पंक्ति के लिए एक व्युत्पन्न के साथ एक फ़ंक्शन को उठाने और एक डिसीमुलेटर के पाठ के साथ अंतर तालिका को भरने के लिए करें। कलाकृतियों को संदर्भ विघटन, स्थानांतरण, कॉल कन्वेंशन, संकलन जांच, व्यवहार तुलना और प्रत्येक भिन्न पंक्ति के लिए एक निर्णय की आवश्यकता होती है।
जाँचें
डेमो और निर्धारक सूट चलाएंः
bashcd phases/13-tools-and-protocols/34-lifting-to-pseudocode
python3 code/main.py
python3 -m unittest discover -s code/tests -vसूट में 25 परीक्षण हैं। सत्यापन साबित करता हैः
- डिकोडर प्रतिबद्धता से सहमत है
llvm-objdumpदोनों वस्तुओं के प्रत्येक निर्देश पर पाठ, और एक लाइव के साथllvm-objdumpजब यह स्थापित किया जाता है - REX, ModR/M, SIB, और विस्थापन क्षेत्र ऑफसेट
leaq (%rip), %rcxSDM में वर्णित के रूप में डिकोड किए जाते हैं - उपसमूह के बाहर एक opcode उठता है
DecodeErrorअनुमान लगाने के बजाय - ELF रीडर में चार कार्य और तीन कार्य सूचीबद्ध हैं
.textस्थानांतरण totalपांच ब्लॉक है,0x44प्रभुत्व रखता है0x50और0x50एक लूप हेडर है जो शरीर के रूप में खुद को%rdiऔर%rsiबनेंparam_1औरparam_2औरparam_1मेंtotalहैint *testऔरcmpके साथje,jle,jaऔरjgeबनें==,<=, अघोषित>और<- एसएसए दो लाइव phi कार्यों को लूप हेडर पर रखता है
totalऔर कोई भी नहींpercent_change - का छद्म कोड
percent_change,gradeऔरtotalअपेक्षित पाठ के बराबर gradeपढ़ता हैparam_1 / 10 - 6पहचान के साथ और कच्चे गुणन के साथ पहचान से बाहर0x66666667shift 34 से 10 तक, shift 33 से 5 तक, और पहचान ग्रिड पर बनी रहती है- पर स्टैक स्लॉट
-O0बनेंlocal_4,local_8,local_c, और फ्रेम पॉइंटर कभी नहीं दिखाई देता .rodata+0x14पढ़ता है1, 2, 3, 4, 4स्थानांतरण के माध्यम से- अप्रत्यक्ष कूद में
gradeपर-O0अपने चार अपहृत ब्लॉक के साथ रिपोर्ट किया जाता है - Ghidra फिक्स्चर में 16 बाइट रिटर्न होता है,
param_3, अनसुलभxor, औरlong param_1 - छठी उत्सर्जित इकाइयों के साथ संकलित
clang -std=c11 -Wall -Werrorके लिएx86_64-unknown-linux-gnu - उठाए गए कार्यों, मरम्मत की गई गिद्रा पाठ, और स्रोत इनपुट ग्रिड पर एक ही 232 पंक्तियों प्रिंट
उत्पादन विफलता मोड
| Failure | What the weak reading reports | What the derivation must show |
|---|---|---|
| Decompiler widens the return value | "returns a 16-byte struct" | idivl writes %rdx, and no caller fixes the width |
| Decompiler adds a parameter | "takes three arguments" | The register is read on one path only because the return width was guessed |
| Idiom goes unrecognized | "multiplies by a magic constant" | The multiplier is the ceiling of 2^s / d and the error bound holds |
Idiom is recognized and a xor is lost | "default depends on the input" | xorl %eax, %eax writes zero, two spellings of one bit cancel |
| Pointer typed as integer | "takes a long" | A scaled 4-byte load from the register is pointer evidence |
| Table length unknown | "reads past the table" | The next referenced offset or the section end bounds it, and the range check agrees |
| Loop shape misread | "while loop" | The header has no exit, the latch jumps back, so it is do while |
| Indirect jump guessed | "switch with five cases" | The targets come from .rela.rodata, or the jump is reported as unresolved |
| Substitution across a store | "one expression" | A loaded value moved past a store or a call changes meaning |
| Refinement model edits the text | "compiles and looks right" | Build rate up, behaviour match down, invented guards replaced visible unknowns |
| Decoder guesses an unknown opcode | "disassembly looks fine" | DecodeError with the address and the byte, then one table entry checked against llvm-objdump |
परिचालन नियम
एक प्यूडोकोड को एक व्युत्पन्न के रूप में देखें जिसे आपने नहीं देखा है। इसे एक लिफ्टर के साथ निर्देशों से पुनः पेश करें जिसे आप पढ़ सकते हैं। दोनों पाठों को एक साथ रखें, और प्रत्येक भिन्न पंक्ति के पीछे निर्देश या अनुमान लिखें। दोनों को संकलित करें और जब आपके पास हो तो दोनों स्रोत के खिलाफ चलाएं। इसके पीछे कोई निर्देश नहीं है, तो यह एक अनुमान रहता है चाहे वह कितना भी साफ पढ़ा जाए।
आगे पढ़ना
- Intel SDM Volume 2, chapter 2, Instruction Format
- System V AMD64 psABI, section 3.2, Function Calling Sequence
- Cytron et al., Efficiently Computing Static Single Assignment Form and the Control Dependence Graph, TOPLAS 1991
- Cooper, Harvey, Kennedy, A Simple, Fast Dominance Algorithm, 2001
- Cifuentes, Reverse Compilation Techniques, PhD thesis, QUT 1994
- Yakdan et al., No More Gotos, NDSS 2015
- Granlund, Montgomery, Division by Invariant Integers using Multiplication, PLDI 1994
- Lee, Avgerinos, Brumley, TIE, NDSS 2011
- Noonan, Loginov, Cok, Polymorphic Type Inference for Machine Code, PLDI 2016
- LLM4Decompile, EMNLP 2024
- DecompileBench, Findings of ACL 2025
- Liu, Raff, Micinski, When LLM Decompilers Recompile More and Preserve Less, 2026
- rea 6.5.0 Ghidra provider notes
- Tag पर स्रोत
3b79af2कि यह पाठ वर्णन करता हैःsrc/domain/functionComparisonDimensions.ts,website/evidence/dx-ball-sound-pan.md. .
This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.
Browse the complete course catalog or open this lesson on GitHub.