Phase 13: Tools & Protocols

رفع به کد پشو: IR، SSA و ساخت از ابتدا

محصول Decompiler یک ادعای با یک مشتق پنهان است. وقتی که شما مشتق خود را بسازید، هر خط از آن محصول تبدیل به چیزی می شود که می توانید بررسی کنید.

Type: Build

Languages: Python, C

Prerequisites: Phase 13 · 32 (ELF from scratch), Phase 13 · 33 (disassembly and control flow)

Time: ~150 minutes

اهداف یادگیری

  • لیفت دستورالعمل های x86-64 را به یک IR کوچک با پرچم های صریح، بار، فروشگاه ها، شاخه ها، تماس ها و بازپرداخت ها رمزگذاری کرد.
  • به سمت فاتر های فریم تبدیل کنید به متغیرهای محلی و به پارامترهای سیستم V ثبت کنید.
  • SSA حداقل با عملکردهای phi در مرزهای تسلط بسازید، سپس جایگزین، ساده سازی و حذف کد مرده کنید.
  • بهبودif،ifباelse،whileوdo whileاز نمودار جریان کنترل با پس از کنترل کننده ها و لبه های عقب.
  • ضرب را با توضیح بده0x66666667به عنوان تقسیم به 10 و اثبات هویت.
  • کد جیدرا را برای یک تابع خط به خط بخوانید و دستورالعمل یا حدس را که پشت هر تفاوت است نام دهید.

مشکل

تو از " ريا " از رمز مخففي "percent_changeدرledger-x86_64-O1.o. گيدرا 12.1.4 در حدود 10 ثانیه پاسخ داد:

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ارزش ها و بازده ها 1 int.Ghidra یک مقدار بازگشت 16 بایت و یک پارامتر سوم را گزارش می دهد که منبع ندارد. شصت بایت کد ماشین درست است و Ghidra هر یک از آنها را به درستی رمزگذاری کرده است. امضا حدس است و هیچ چیز در متن آن را به عنوان یک نشان می دهد.

rea این متن را به عنوان procedure_pseudo_codeدر این نسخه، یک محدودیت به ثبت متصل می شود. محدودیت می گوید: "Pseudocode Ghidra decompiler output است، نه منبع اصلی و نه متن معادل output Hopper".compare_functionsابزار فرقی pseudocode به عنوان خطوط متن دارد. ثبت ابعاد خود می گوید "اجمع متن ارائه دهنده غیر شفاف است" و عادی سازی نقل مکان در دسترس نیست. rea نمی تواند به داخل متن نگاه کند و همچنین یک عامل که فقط آن را می خواند نمی تواند.

گزارش DX-Ball از rea نشان دهنده هزینه است.FUN_00406400(void)تماس گرفتن__ftol(). در نظرسنجی دستورالعمل یک ورودی ستک و یک عبارت x87 نشان داده شد. عامل فقط به دلیل اختلاف نظر دو دیدگاه ادامه داد.

این درس مشتق را که تجزیه کننده پنهان می کند، می سازد. شما از 56 دستورالعمل که درس 33 رمزگذاری کرد شروع می کنید، آنها را به یک IR بالا می بردید، به SSA نام می برد، ساده سازی و ساختار می کنید. سپس کد خود را در کنار Ghidra قرار می دهید.percent_change،gradeوtotalتست ها محصول شما را با clang جمع آوری می کنند و آن را با منبع در یک شبکه ورودی اجرا می کنند.

"بند و کلام"

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 هر شی و دستور REA که وسایل Ghidra را تولید کرده است، ذکر می شود. ساخت باایت به باایت در زنجیره ابزار متصل شده بازتولید می شود.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .llvm-objdump -d -rمتن آن برای هر دو موضوع به عنوانledger-x86_64-O1.objdump.txtوledger-x86_64-O0.objdump.txtیک آزمایش این فایل ها را تجزیه و تحلیل می کند و هر دستورالعمل را با آدرس، با بیت و با متن AT&T مقایسه می کند. این شامل تمام 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

مرحله اول: بایت ها به دستورالعمل ها

درس 33 کد کدر را ساخت. این درس یک کپی کوچک را نگه می دارد تا کد به طور خودکار اجرا شود. کپی دقیقاً کدهای کدر را پوشش می دهد که برای آن پخش می شود.ledger.cدر-O0و-O1. براي هر چيزي که بيشتر به وجود ميادDecodeErrorبا آدرس و بايت کد آپکود

کد کدگر به ترتیب بایت های SDM اینتل، جلد 2 بخش 2.1 را دنبال می کند. پیشگویی ها اول، سپس کد opcode، سپس ModR/M، سپس SIB، سپس جابجایی، سپس فوری هستند. بخش 2.2.1 پیشگویی REX را اضافه می کند، یک بایت از 0x40به0x4FREX.W یک اپراند ۶۴ بیت را انتخاب می کند، REX.R regدر این زمینه، REX.X شاخص SIB را گسترش می دهد و REX.B را گسترش می دهد r/mبخش 2.1.3 سه میدان ModR/M را تعریف می کند.mod،regوr/m، و سه میدان SIBscale،indexوbase. در حالت 64 بتmod = 00باr/m = 101یک تغییر 32 بیت نسبت به دستورالعمل بعدی (قسم 2.2.1.6) است.

Opcode bytesFormFixture example
01 29 31 39 85 89op Ev, Gv29 fe subl %edi, %esi
03 2b 3b 8b 8dop Gv, Ev03 04 97 addl (%rdi,%rdx,4), %eax
63 with REX.Wmovsxd Gv, Ev48 63 c7 movslq %edi, %rax
69 and 6bimul Gv, Ev, imm48 69 c0 67 66 66 66
83 /0 /5 /7add sub cmp Ev, imm883 c1 fa addl $-0x6, %ecx
c1 /5 /7shr sar Ev, imm848 c1 f9 22 sarq $0x22, %rcx
f7 /7idiv Evf7 ff idivl %edi
ff /0 /4inc Ev, jmp Ev48 ff c2 incq %rdx
70 to 7f, eb, e8jcc rel8, jmp rel8, call rel3274 09 je 0xd
99, c3, 90, 0f 1fcltd, retq, nop66 0f 1f 44 00 00 nopw (%rax,%rax)

.leaq (%rip), %rcxدرgrade+0x23هفت بايت با صفر جابجا شدن است.R_X86_64_PC32.rodata+0x10در تعویض0x36، میدان حرکت را بالا می برد و در مرحله سوم حلش می کند

مرحله دوم: بلوک ها و حاکمان

یک بلوک پایه یک ردیف اظهارات با یک ورودی در بالا و یک خروج در پایین است. این تعریف از آلن (1970) است و هر کاغذ تجزیه کننده آن را دوباره استفاده می کند. build_blocks()به عنوان یک بلاک، هر یک از کلک ها به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به عنوان یک کلک به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور کامل به طور قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل قابل اعتماد است.jmp. یکتا دارهretهیچ کدام از آنها وجود ندارد و یک بلوک که با یک دستورالعمل معمولی به پایان می رسد به رهبر بعدی می رسد.

totalدر-O1پنج بلوک دارد:

BlockInstructionsSuccessors
0x40testl %esi, %esi then jle 0x5c0x5c, 0x44
0x44movl %esi, %ecx, xorl %edx, %edx, xorl %eax, %eax, nopw0x50
0x50addl (%rdi,%rdx,4), %eax, incq %rdx, cmpq %rdx, %rcx, jne 0x500x50, 0x5b
0x5bretqexit
0x5cxorl %eax, %eax then retqexit

immediate_dominators()الگوریتم تکراری کوپر، هاروی و کندی (2001) است. این بلوک ها را در بعد از ترتیب معکوس حرکت می دهد و حاکمان پیشروهای پردازش شده را تا زمانی که هیچ چیز تغییر نمی کند، متقاطع می کند. در نمودار های کوچک این اندازه، آن را در دو گذر انجام می دهد. برایtotalمیگه0x44تسلط داره0x50. همچنین میگه که0x50خود را از طریق لبه عقب تسلط می دهد، که این همانطوری است که مرحله 8 حلقه را پیدا می کند.

همون تابع دو برابر بیشتر اجرا میشهdominance_frontiers()در مرحله 5 از آن برای SSA استفاده می کند.post_dominators()این را روی نمودار معکوس با یک گره خروج مجازی اجرا می کند تا در مرحله 8 ترکیب ها را پیدا کند.

مرحله سوم: بالا بردن به یک IR کوچک

IR شش نوع اعلامیه دارد:Assign،Store،Branch،Jump،IndirectJumpوReturn.عبارات ثابت، متغیر، عملیات دوگانه با عرض، کاست (sext،zext،truncپنج شکل دیگر وجود دارد که بعداً ساده تر می شود:Flags،Cond،Concat،SignFillوSymbolic. .

ثبت نامها متغیرهای نامگذاری شده توسط خانواده 64 بت هستند.%eax.Assignبهraxبا عرض 32 و خواندن%eaxدر مورد "raxدر حالت 64 بت، نوشتن 32 بیت صفر را به داخل کل ثبت می کند، بنابراین یک خواندن 64 بیت بعدی با همان مقدار یک zext. بلند کننده تعریف رسیدن را نمی داند، بنابراین عرض خواندن را ثبت می کند و اجازه می دهد SSA در مرحله 5 کاست را وارد کند.

InstructionIR statements
subl %edi, %esiflags = cmp(esi, edi) then esi = esi - edi
testl %edi, %ediflags = test(edi, edi)
je 0xdif je(flags) goto 0xd else goto 0x4
xorl %eax, %eaxeax = 0
cltdedx = signfill(eax)
idivl %edidividend = edx:eax, eax = dividend / edi, edx = dividend % edi
movslq %edi, %raxrax = sext64(edi)
sarq $0x22, %rcxrcx = rcx >>s 34
shrq $0x3f, %raxrax = rax >>u 63
addl (%rdi,%rdx,4), %eaxflags = add(eax, load32(rdi + rdx * 4)) then eax = eax + load32(rdi + rdx * 4)
leaq (%rip), %rcx with its relocationrcx = &.rodata+0x14
pushq %rbp, movq %rsp, %rbp, subq $0x10, %rsp, popq %rbpnothing, frame bookkeeping
retqreturn rax

پرچم ها متغیر هستند

هر دستور که کد شرط را تنظیم می کند یک متغیر به نام را می نویسدflags، و هر پرش مشروط آن را می خواند.cmp a, bنوشته شدهcmp(a, b)، یکtest a, bنوشته شدهtest(a, b)وsubهمش رو مي نويسهcmpشکل به عنوان یک اثر جانبی.jccمی شهBranch(Cond(cc, flags), target, fallthrough)کد شرط، وقتی که این دو در مرحله 6 ملاقات کنند، عامل مقایسه را تعیین می کند:

CodeAfter cmp(a, b)After test(a, a)
jea == ba == 0
jnea != ba != 0
jl, jge, jle, jgsigned <, >=, <=, >signed compare with 0
jb, jae, jbe, jaunsigned <, >=, <=, >unsigned compare with 0

xorl %eax, %eaxبه سمت راست بالا می رودeax = 0پرچم هاي که مي بندازن نوشته مي شه:addپرچم هاي سبکي که هيچ پرش در دستگاهي نميخوادxorزبان مهمهgrade، جایی که گیدرا از دستش داد

کلتید و آی دی وی

cltdپر کردن%edxبا علامت "بيت"%eax.idivl %ediدوتا 64 بت را تقسیم می کند %edx:%eaxاز طرف%edi، مقدار را وارد می کند%eax، و بقيه در%edx. بلند کننده جفت رو به نام موقت مي نويسهdividendاول، بقيه جمله بعد از اون نوشته هاي قديمي%eaxدر SSA نصف بالا به عنوان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. .

دستورالعمل های فریم خود هیچ IR تولید نمی کنند و نه هم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. گزارش هاي IR ميگنrcx = &.rodata+0x14و اون ميز پنج تا رو نگه دارهintارزش ها1, 2, 3, 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: SSA، یک نام در هر تعریف

بعد از بلند شدنgradeنوشته شدهraxچهار بار وrcxسه بار سیترون، فرانت، روزن، وگمن و زادک (1991) شکل واحد اختصاصی ثابت را تعریف می کنند. هر متغیر دقیقا یک بار اختصاص داده می شود و تابع های phi در نقاط پیوستن از بین تعاریف که به آنها می رسد انتخاب می کنند. ساخت آنها دو مرحله ای دارد. بخش 5.1 تابع های phi را با استفاده از مرزهای تسلط قرار می دهد و بخش 5.2 هر تعریف و استفاده را با پیاده روی روی روی درخت تسلط تغییر نام می دهد.insert_phis()وrename()از اين دو قسمت پيروى کنيد

لیست SSA از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 + 164 بایت است، پس فیهrdx_2در طول طول طول طولش بیشتر میشه%rcxدرcmpq %rdx, %rcx64 بيت است در حالی که تعریف آنecx_1 = esi_032 بت است، بنابراین استفاده می شودzext64(ecx_1)هر بازیگری که در کد آخر قرار گرفته از این قانون است

قسمت "لایت" راه خروج از SSA است.coalesce_webs()این کار برای اشکال که یک کامپایلر برای این عملکردها منتشر می کند کار می کند. این می تواند بر روی نسخه های موازی شکسته شود، جایی که دو phis در یک با ارزش های مبادله متحد می شوند و درس این مورد را اداره نمی کند. بخش 7 سایترون خروج عمومی را از طریق رنگگذاری توصیف می کند.

مرحله 6: ساده سازی، جایگزینی، حذف کد مرده

optimize()سه بار عبور می کند تا هیچ چیز تغییر نکند. ساده کننده عبارت ها را با این قوانین دوباره می نویسد:

BeforeAfter
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-bitx
trunc32(a + b) on 64-bit operandstrunc32(a) + trunc32(b)
(signfill(x): x) / dx / d at width 32
load32(base + idx * 4)base[idx]
trunc32(p >>s 34) + trunc32(p >>u 63) with p = sext64(x) * 0x66666667x / 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_1بهeax_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کف از صفر دور می شود و C به سمت صفر دور می شود.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;

هر دو مرتب و هر دو محاسبه همان ارزش ها. کلید را نگه دارید. وقتی کد را از یک مرتب کننده که شما مطالعه نکرده اید می خوانید، فرم خام صادقانه است.

مرحله هفتم: انواع استفاده

کد ماشین دارای عرض و بدون نوع است.infer_types()این شبکه که 32 بیت عرض دارد،int، و یک وب 64 بایت استlong. یک وب که پایه ی دسترسی عنصر استbase[idx]با عناصر 4 بايتint *. نوع اشاره به طول کپی ها و phis پخش می شود، بنابراین 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 *. .

لی، آویگرینوس و بروملی (TIE، NDSS 2011) بازیافت نوع فریم به عنوان حل محدودیت در یک شبکه. نتیجه یک مرز بالا و پایین به جای یک حدس است. نونان، لوگینوف و کوک (Retypd، PLDI 2016) افزودن پلیمورفیسم و تکرار. آنها گزارش 98 درصد به یاد آوردن در .constاین درس از دو قانون ارزان ترین از آن خط کار استفاده می کند و نام هایی را که نمی توانند ببینند.

حسابداری بدون امضا در عبارت ها و نه در انواع متغیر نگهداری می شود. jaبعد ازcmpl $0x4, %ecxچاپ شده(unsigned int)ecx_1 > 4وshrqچاپ شده(unsigned long)rax_1 >> 63متغیرها امضا می شوند تا خواننده ارزش ثبت را و تنها جایی که در آن دستورالعمل به عنوان امضا نشده دیده شود.

مرحله ۸: ساخت از نمودار

سیفوانتز (1994) ، فصل 6، مناطق نمودار را با شکل های یک زبان سطح بالا مطابقت می دهد. حلقه ها اول (6.6.1) ، سپس شرایط دو طرفه (6.6.2) ، سپس شرایط n طرفه (6.6.3) هستند. یاکدان، اِشویلر، گِراردز-پادیلا و اسمیت (NDSS 2015) از جاده ی دیگر استفاده می کنند. آنها شرایطی را که هر گره تحت آن اجرا می کند، بدست می آورند و ساختار را از این شرایط بدون اشکال ثابت بسازند. انگیزه شان اندازه گیری است. هیکس ریز در یک نمونه تولید شد 1,571 gotoبیانات در 49514 خط، یک در هر 32 خط، و ساختار آنها هیچ یک از 1530 تابع اصلی را تولید نکرد.

structure()یک عضو کوچک از خانواده اول است. به دو واقعیت از مرحله 2 نیاز دارد.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با یک جانشین خارج از حلقه، پخش کنیدwhile (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. .

-O1 با سوئیچ چیکار کرد؟

در-O1کلنگ يه ميز پرش رو نگه نداشتgrade. حسابش رو انجام دادscore / 10 - 6و نتیجه را با 4 به عنوان یک عدد غیرموقعه مقایسه کرد. سپس ارزش بازگشت را از جدول پنج ورودی از intدر.rodata+0x14. مقایسه بدون امضا يه دستوريه که هر دو رو پوشش ميدهscore / 10 < 6وscore / 10 > 10تفاوت منفی یک مقدار بزرگ بدون امضا است، بنابراین همان چک را شکست می دهد.

میز نگه داره1, 2, 3, 4, 4برای کوتیوینت های 6 تا 10switchبا پنج تاreturnاعلامیه ها تبدیل به یکifو یک صف خوانده شده. بلند کننده دقیقاً همین را منتشر می کند و نام میز را می دهدlookup_14پس از تعویض بخش آن

طول جدول یک هوریستیک است.table_values()از جدول شروع به جدول بعدی.rodataاز هر چیزی تعویض می کنه.textدر این شی، آفسیت بعدی که به آن اشاره می شود 0x28، جدولgrade_label، پسlookup_14.(0x28 - 0x14) / 4 = 5.از نظر نميشه با 4 مقایسه کرد .گيدرا طولش رو هم نميدونه*(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4)، یک آدرس بائیت به علاوه یک تعویض، و پنج مقدار را برای خواننده برای نگاه کردن به بالا.

کنار هم با گیدرا

هر دو تا از نسخه های دمو چاپ شده متن گیدرا ازcode/fixtures/ghidra/*.json، شواهد ثبت کرده که ریا در سال 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. متن مشاهده می شود. ادعاهای درون متن حاصل شده و اسناد نمی گویند کدامین.

%_change

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;
                                               | }
DifferenceInstruction or guess
Return type undefined1 [16] against intGuess. idivl writes %rax and %rdx, and no caller fixes the return width. Both registers were kept.
param_3Guess 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_1Instruction. 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 keptFollows 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 itInstruction 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 << 0x40Guess. 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;                                | }
}                                              |
DifferenceInstruction or guess
param_1 / 10 - 6 in bothInstruction. Both recognized the 0x66666667 idiom.
uVar1 = -(param_1 >> 0x1f) ^ (uint)(... >> 0x3f) against eax_1 = 0Instruction 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 <= 4Instruction 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 intGuess 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;                                |
}                                              |
DifferenceInstruction or guess
int *param_1 against long param_1Inference on our side from addl (%rdi,%rdx,4), %eax. Ghidra kept the width.
uint param_2 and (int)param_2 against int param_2Ghidra chose unsigned because cmpq %rdx, %rcx compares the zero-extended count. We kept int and cast at the compare.
do { } while in bothInstruction 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 != uVar2Our 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_1Instruction. movl %esi, %ecx zero-extends, and the 64-bit compare reads the full register.

آزمون رفتار متن Ghidra را برای gradeوtotalبدون تغییر، دو تا اضافه میشهtypedefخط ها و تعریفDAT_001000b4از بیست بايت در .rodata+0x14هر دو تابع با منبع شبكه مطابقت دارند

آنچه تحقیقات درباره خواندن سئودوکوید می گویند

LLM4Decompile (EMNLP 2024) تنها Ghidra را با 20.12 درصد قابلیت اجرای مجدد در HumanEval-Decompile اندازه گیری کرد. حدود یک نفر از هر پنج تابع، ادعاهای همراه با معیار را دوباره جمع آوری و از آن عبور می کند. با اصلاح تولید گیدرا با مدل 6.7B، این رقم به 52.74 درصد رسید. DecompileBench (Findings of ACL 2025) شش decompiler صنعتی و شش رویکرد LLM را در 23،400 عملکرد از 130 برنامه اجرا کرد. نتایج LLM بهتر بود و 52.2 درصد کمتر از عملکردی درست بود.

لیو، راف و میسینسکی (arXiv 2609.05370, سپتامبر 2026) این شکاف را توضیح می دهند. آنها از ۳۰۰ تابع کتابخانه گیت هاب و ۲۸۷ تابع مبتنی بر CVE استفاده کردند. قوی ترین مدل اصلاحات، میزان ساخت گیدرا را از 75 تا 90 درصد افزایش داد در حالی که میزان مطابقت رفتار از 74 تا 62 درصد کاهش یافت. کاندیداهای که در هر آزمون در معیار معیار عبور کردند هنوز هم در مورد ورودی های گیج شده متفاوت بودند. این اتفاق در ۴.۹ درصد موارد در هشت سیستم و تا ۱۳ درصد برای یک سیستم رخ داد. تا یک دهم آسیب پذیری های آشکار شده از کد دقیق ناپدید شده اند.

علت آن ها ساده است. زمینه ها، انواع، کلیه ها و نگهبانان اختراع شده، جای نامعلوم های قابل مشاهده ای را که در متن آن ها باقی مانده بود، جایگزین می کنند.

به همین دلیل است که اون پرینت بلند کننده(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این IR را قبل از SSA، لیست SSA را با تابع های phi و کد پتوکو در کنار متن Ghidra چاپ می کند. سپس چاپ می کند 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در این ترتیب:

  1. read_object()سرپرستی بخش ها، جدول نماد ها و .relaبخش هایی از یک شی که می تواند در ELF64 تغییر مکان کند.
  2. decode_one()وrender_att()یک دستور رو رمزگشایی کن و آن را به این ترتیب چاپ کنllvm-objdumpآره
  3. build_blocks()،immediate_dominators()وdominance_frontiers()و اطلاعات را به صورت نمودار بسازید.
  4. lift_insn()تبدیل یک دستورالعمل به بیانات IR و حل نقل مکان ها.
  5. insert_phis()وrename()ساخت SSA و وارد کردن قطعات عرض
  6. make_simplifier()،substitute_once()وremove_dead_code()داخل رو ببريoptimize(). .
  7. collect_params()،coalesce_webs()وinfer_types()نام و تایپ شبکه ها
  8. structure()باpost_dominators()وnatural_loops()بدن C رو پخش ميکنه
  9. lift_function()مرحله ها رو با هم ببندد وlift_all()تمام اشیا را با امضا کالی بلند می کند.

ازش استفاده کن

برای بلند کردن یک تابع از جسم خود، آن را با clang -target x86_64-unknown-linux-gnu -cو تماس بگیریدlift_all(read_object(path)). وقتي که ديكودر بلند بشهDecodeError، باط opcode را از پیام بخوانید و آن را در نقشه opcode SDM پیدا کنید. سپس یک ورودی را به جدول های بالای فایل اضافه کنید. هنگامی که بلند کننده روی یک mnemonic بالا می رود، یک قانون را به lift_insn()وقتی سازنده یکgoto، گراف رو با چاپ کنIrPrinterو تصمیم بگیرید که کدام قانون شکل گم شده است. هر اضافه کوچک است و هر یک با آن بررسی می شودllvm-objdumpیا از طریق آزمایشات موجود.

هنگامی که Ghidra نصب شده است، rea را روی همان تابع اجرا کنید و دو متن را کنار هم قرار دهید. قبل از اینکه به هر یک از متن ها باور کنید، جدول تفاوت را پر کنید. تفاوت با یک دستورالعمل پشت آن انتخاب ارائه است. تفاوت بدون دستورالعمل پشت آن حدس است و حدس می تواند از هر دو طرف باشد.

آزمایشگاه تعاملی

آزمایشگاه A: یک تابع از طریق پنج مرحله

از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, %esiبهesi_1 = esi_0 - edi_0به(param_2 - param_1)بايتfeModR/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 خطای خطایی را شکست می دهد.

آزمایشگاه C: یک شی جدید کد کش را متوقف می کند

دستگاه را در -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. کد آپکود81این فرم گروه 1 با یک 32 بت فوری است.andl $0x7ffffff8, %edx، و کلنگ ازش در حلقه متراکي استفاده کردtotal. اضافهش کن کنارش0x83درdecode_one()، تکرار کنید و توجه کنید که کد آپکد بعدی شما را متوقف می کند. زیر مجموعه یک ورودی در یک زمان رشد می کند و هر ورودی با llvm-objdump. .

آزمایشگاه D: ارزش پیش فرض Ghidra در درجه

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در دو بايت ميگه

آزمایشگاه تمرین

اضافه کردنswitchبازپرداخت برایgradeدر-O0. .

الزامات:

  • پنج نقل مکان رو در سال.rela.rodataازledger-x86_64-O0.o، هر یکR_X86_64_PC32.text+offset، و پنج هدف مورد را از جدول در .rodata+0x0. .
  • اضافه کنیدSwitchدر این حالت، این گزینه به عنوان یک "مجموعه" در IR با عبارت شاخص، لیست هدف و پیش فرض از jaقبل از بارگذاری میز
  • به بلوک سوئیچ یک جانشین در هر هدف بده تا هراسنده ها، هراسنده ها و SSA همچنان کار کنند.
  • پخش کنswitch (local_10) { case 0:... }با يک نفرcaseهر هدف و اپيلوگ مشترک بعد از پیوستن
  • اون نوتی رو برای هر پرشی غیر مستقیم که نمیتونی میزش رو ببندی نگه داری
  • نتیجه را با clang جمع آوری کنید و آن را با منبع در شبکه امتیاز در آزمون ها مقایسه کنید.

موفقیت به معنیgradeدر-O0وgradeدر-O1تولید همان خروجی در شبکه، با یک switchو نهgoto. .

آثار هنری

این درس به ما کمک می کندoutputs/skill-lifting-to-pseudocode.md. از آن برای رفع یک تابع با یک مشتق برای هر خط و برای پر کردن جدول تفاوت با متن یک decompiler استفاده کنید. این آرتیفکت نیاز به تجزیه مرجع، نقل مکان، کنوانسیون تماس، بررسی جمع آوری، مقایسه رفتار و حکم برای هر خط متفاوت دارد.

بررسی کنید

نمایش و مجموعه تعیین کننده را اجرا کنید:

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), %rcxبه گونه ای که در SDM توصیف شده، رمزگذاری می شوند
  • یک کد opcode خارج از زیر مجموعه را افزایش می دهد DecodeErrorبجاي حدس زدن
  • خواننده ELF چهار تابع و سه تابع را فهرست می کند .textنقل مکان
  • totalپنج بلوک داره0x44تسلط داره0x50و0x50یک سر و سر حلقه است که به عنوان بدن خود
  • %rdiو%rsiتبدیل شدنparam_1وparam_2وparam_1درtotal.int *
  • testوcmpباje،jle،jaوjgeتبدیل شدن==،<=، امضا نشده>و<
  • SSA دو تابع Phi زنده را در سر سر حلقه قرار می دهدtotalو هيچکس درpercent_change
  • کد پشویدوکد percent_change،gradeوtotalبرابر با متن انتظار می رود
  • gradeمیخوادparam_1 / 10 - 6با تشخیص و ضرب خام با تشخیص غیر
  • 0x66666667با شیفت 34 به 10 تقسیم می شود، با شیفت 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
  • عملکردهای بلند شده، متن Ghidra اصلاح شده و منبع همان 232 خط را بر روی شبکه ورودی چاپ می کنند

روش های شکست تولید

FailureWhat the weak reading reportsWhat 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

قاعده عملیاتی

کد فرضی را به عنوان مشتق که ندیدید، نگاه کنید. آن را با یک آسانسور که می توانید بخوانید، از دستورالعمل ها بازتولید کنید. دو متن را کنار هم قرار دهید و دستورالعمل یا حدس را پشت هر خط متفاوت بنویسید. هر دو را جمع آوری کنید و هر دو را در برابر منبع اجرا کنید. یک خط بدون دستورالعمل پشت آن یک حدس باقی می ماند، مهم نیست که چقدر تمیز خوانده شود.

خواندن بیشتر

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.