Phase 13: Tools & Protocols

El levantamiento al Pseudocodo: IR, SSA y estructuración desde cero

Cuando construyes la derivación tú mismo, cada línea de esa salida se convierte en algo que puedes comprobar.

Type: Build

Languages: Python, C

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

Time: ~150 minutes

Objetivos de aprendizaje

  • El elevador decodificó instrucciones x86-64 a una pequeña IR con banderas explícitas, cargas, tiendas, ramas, llamadas y devoluciones.
  • Convierta los espacios de marco en variables locales y los registros de argumentos del Sistema V en parámetros.
  • Construye un SSA mínimo con funciones phi en las fronteras de dominio, luego sustituye, simplifique y elimine el código muerto.
  • Recuperarseif¿ Qué ?ifconelse¿ Qué ?while, y do whilede la gráfica de flujo de control con postdominadores y bordes traseros.
  • Explica el multiplicador por 0x66666667como una división por 10 y probar la identidad.
  • Lea el pseudocodo de Ghidra para las mismas funciones línea por línea y nombre la instrucción, o la suposición, detrás de cada diferencia.

El problema

Le preguntaste a Rea el pseudocodo de percent_changeEn elledger-x86_64-O1.oGhidra 12.1.4 respondió en unos diez segundos:

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;
}

La fuente tiene cuatro líneas de largo.intvalores y devoluciones uno intGhidra informa un valor de retorno de 16 bytes y un tercer parámetro que la fuente no tiene. Los dieciséis bytes de código de máquina son correctos, y Ghidra decodificó cada uno de ellos correctamente. La firma es una conjetura, y nada en el texto lo marca como uno.

rea devuelve este texto como el procedure_pseudo_codeEl resultado y se une una limitación al registro. La limitación dice "Pseudocode es Ghidra decompiler salida, no fuente original y no texto equivalente a Hopper salida".compare_functionsLa herramienta difiere de los pseudocodes como líneas de texto. Su propio registro de dimensiones dice "Assembly es texto opaco del proveedor" y que la normalización de la reubicación no está disponible. rea no puede mirar dentro del texto, y tampoco puede un agente que sólo lo lea.

El registro de DX-Ball de rea muestra el costo.FUN_00406400(void)llamando__ftol()La vista de instrucciones mostró una entrada de pila y una expresión x87. El agente siguió funcionando sólo porque las dos vistas no estaban de acuerdo.

Esta lección construye la derivación que el decompiler oculta. comienza con las 56 instrucciones que la lección 33 decodificó, elevalas a un IR, renombre en SSA, simplifica y estructura. Luego coloca su pseudocodo junto al de Ghidra parapercent_change¿ Qué ?grade, y totalLas pruebas compilan su salida con clang y ejecutarlo contra la fuente en una cuadrícula de entrada.

La Fijación y el Oráculo

code/fixtures/ledger.ces el elemento compartido de esta parte del curso:

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_labelLas tres funciones anteriores son las que esta lección prueba.

El objeto fue construido con clang -target x86_64-unknown-linux-gnu -O1 -fno-asynchronous-unwind-tables -c ledger.c- El .-O0el objeto se sienta al lado para la sección de la ranura de pila. code/fixtures/README.mdEn el código de configuración de la red de datos de Ghidra se incluyen los comandos SHA-256 de cada objeto y el comando rea que produjo las fichas de Ghidra.

El oráculo para el decodificador es llvm-objdump -d -r. Su texto para ambos objetos se compromete como ledger-x86_64-O1.objdump.txty ledger-x86_64-O0.objdump.txtUna prueba analiza esos archivos y compara cada instrucción por dirección, por bytes y por texto de AT&T.-O1y todas las instrucciones en -O0- ¿ Cuándo ?llvm-objdumpse instala en el camino fijado, una segunda prueba lo ejecuta en vivo y compara de nuevo.

Aquí está .percent_changeComo lo imprime el oráculo:

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

Etapa 1: Bytes a las instrucciones

La lección 33 construyó el decodificador. Esta lección mantiene una pequeña copia para que el código se ejecute por sí mismo. La copia cubre exactamente los códigos opcodes que se emiten paraledger.cEn el-O0y -O1Para cualquier otra cosa que se elevaDecodeErrorcon la dirección y el byte del código op.

El decodificador sigue el orden de bytes del SDM Intel, Volumen 2, sección 2.1. Los prefijos vienen primero, luego el código op, luego ModR/M, luego SIB, luego el desplazamiento, luego el inmediato.0x40¿ Qué ?0x4F, que debe estar directamente antes del código op. REX.W selecciona una operanda de 64 bits, REX.R extiende la regEn el campo de referencia, REX.X extiende el índice SIB y REX.B extiende r/mLa sección 2.1.3 define los tres campos ModR/M mod¿ Qué ?reg, y r/m, y los tres campos SIB scale¿ Qué ?index, y baseEn modo de 64 bits,mod = 00conr/m = 101se refiere a un desplazamiento de 32 bits en relación con la siguiente instrucción (sección 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)

El leaq (%rip), %rcxEn elgrade+0x23Es 7 bytes con un desplazamiento cero.R_X86_64_PC32.rodata+0x10en compensación 0x36El elevador lo resuelve en la etapa 3.

Etapa 2: Bloques y dominadores

Un bloque básico es una secuencia de declaraciones con una entrada en la parte superior y una salida en la parte inferior. Esa es la definición de Allen (1970), y cada papel de descompilador lo reutiliza. build_blocks()Reúne líderes: la entrada de la función, cada objetivo de salto dentro de la función, y cada dirección después de un salto o retorno. Cada bloque obtiene sucesores de su última instrucción.jmpTiene uno.retNo tiene ninguna, y un bloque que termina en una instrucción ordinaria pasa al siguiente líder.

totalEn el-O1tiene cinco bloques:

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()Es el algoritmo iterativo de Cooper, Harvey y Kennedy (2001). Caminará los bloques en orden posterior inverso y intersectará a los dominadores de los predecesores procesados hasta que nada cambie.totalDice que0x44domina .0x50También dice que0x50se domina a través del borde trasero, que es cómo la etapa 8 encuentra el bucle.

La misma función se ejecuta dos veces más. dominance_frontiers()se utiliza para la SSA en la etapa 5. post_dominators()lo ejecuta en el gráfico invertido con un nodo de salida virtual para encontrar uniones en la etapa 8.

Etapa 3: Elevación a una pequeña IR

El IR tiene seis tipos de declaraciones: Assign¿ Qué ?Store¿ Qué ?Branch¿ Qué ?Jump¿ Qué ?IndirectJump, y Return. Expresiones son constantes, variables, operaciones binarias con ancho, lanzamientos (sext¿ Qué ?zext¿ Qué ?trunc), y cargas. existen cinco formas más que se simplificarán más adelante: Flags¿ Qué ?Cond¿ Qué ?Concat¿ Qué ?SignFill, y Symbolic¿ Qué ?

Los registros son variables nombradas por su familia de 64 bits.%eaxes un Assign¿ Qué ?raxcon ancho 32, y una lectura de %eaxes una lectura de raxEn el modo de 64 bits, una escritura de 32 bits se extiende a cero en el registro completo, por lo que una lectura posterior de 64 bits del mismo valor es un zextEl elevador no conoce la definición de alcance, por lo que registra el ancho de lectura y permite a SSA insertar el molde en la etapa 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

Las banderas son una variable

Cada instrucción que establece los códigos de condición escribe una variable llamada flags, y cada salto condicional lo lee.cmp a, bescribe cmp(a, b), una test a, bescribe test(a, b), y subescribe lo mismo cmpforma como efecto secundario.jccSe convierte enBranch(Cond(cc, flags), target, fallthrough)El código de condición determina al operador de comparación cuando ambos se encuentran en la etapa 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, %eaxse levanta directamente a eax = 0Las banderas que pone están escritas como:addLas banderas de estilo que no salta en el accesorio dice.xorEl idioma es importante en grade, donde Ghidra lo perdió.

C.L.T. y I.D.V.

cltdllena%edxcon el signo de %eax- ¿ Qué ?idivl %edidivide el par de 64 bits %edx:%eaxpor %edi, pone el coeficiente en %eax, y el resto en %edxEl levantador escribe el par en un nombre temporaldividendLa declaración restante luego lee la vieja%eaxEn la SSA la mitad superior aparece comosignfillEl simplificador reduce el par a la mitad baja, y la división se convierte en un 32-bit /¿ Qué ?

Las ranuras de pila en -O0

En el-O0Clang mantiene a todos los locales en el marco.percent_changelas tiendas %edi¿ Qué ?-0x8(%rbp)y %esi¿ Qué ?-0xc(%rbp)El levantador vigila para que se encuentren.pushq %rbpseguido por movq %rsp, %rbpDespués de ese prólogo, una ópera de memoria con base.%rbp, sin índice, y un desplazamiento se convierte en una variable denominada local_<hex>- ¿ Qué ?-0x8(%rbp)¿ Es verdad ?local_8¿ Qué ?-0xc(%rbp)¿ Es verdad ?local_c¿ Qué ?

Las instrucciones del marco no producen IR, y tampoco lo hacen.subqy addqEn el%rspCualquier otro operando de memoria se mantiene unLoado Store¿ Qué ?

Traslados

El leaq (%rip), %rcxEn elgradeEl desplazamiento en compensación0x36diceR_X86_64_PC32.rodata+0x10El enlace escribirá .S + A - PEn el campo.Ses la dirección de .rodata¿ Qué ?Aes el agregado 0x10, y PLa instrucción luego calcula next_ip + disp = (P + 4) + S + A - P = S + A + 4¿ Qué ?

El levantador aplica la misma aritmética sin un enlace.0x10 + 4 = 0x14La IR dice quercx = &.rodata+0x14, y la mesa allí tiene los cinco .intvalores 1, 2, 3, 4, 4¿ Qué ?

Etapa 4: Parámetros de la convención de convocatoria

El sistema V AMD64 psABI, sección 3.2.3, pasa los argumentos de clase INTEGER en %rdi¿ Qué ?%rsi¿ Qué ?%rdx¿ Qué ?%rcx¿ Qué ?%r8¿ Qué ?%r9El resultado regresa en%raxY luego ...%rdxEn el apartado 3.2.1 se dice que %rbp¿ Qué ?%rbx, y %r12por el%r15pertenece al que llama y debe ser conservado.

collect_params()aplica una regla: un registro de argumentos que se lee antes de cualquier escritura en la función es un parámetro. El registro más alto de tal fija el recuento, por lo que una función que lee %rsiy nunca .%rditodavía tiene dos parámetros.percent_changeLeía%ediy %esi, así que lo ha hecho .param_1y param_2- ¿ Qué ?gradesólo se lee %edi- ¿ Qué ?totalLeía%rdicomo una base de 64 bits y %esicomo un recuento de 32 bits.

Ahora es Ghidra.param_3Tiene una explicación.idivlescribe %rdx, y el psABI permite una vuelta de 128 bits en INTEGER %rax:%rdxEn un archivo de objetos no hay un llamador que diga cuánto es el valor de retorno.

En el camino cero,xorl %eax, %eaxSólo escribe .%eaxGhidra lee .%rdxallí como una entrada de la función, y esa entrada se convirtió en param_3Las instrucciones son correctas, la firma es una suposición sobre las partes que las instrucciones dejan abiertas.

Etapa 5: SSA, un nombre por definición

Después de levantar,gradeescribe raxcuatro veces y rcx- ¿Qué? - Tres veces. Cytron, Ferrante, Rosen, Wegman y Zadeck (1991) definen la forma de asignación única estática. Cada variable se asigna exactamente una vez, y las funciones phi en los puntos de unión seleccionan entre las definiciones que las alcanzan. Su construcción tiene dos pasos. La sección 5.1 coloca las funciones phi utilizando fronteras de dominio, y la sección 5.2 renombra cada definición y uso con un paseo sobre el árbol dominador.insert_phis()y rename()Sigue esas dos secciones.

La lista de la SSA de totalmuestra lo que produce la construcción:

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

La versión 0 de un registro es su valor al momento de la entrada. esi_0y rdi_0El encabezado del bucle0x50tiene dos predecesores, el bloque de configuración y sí mismo, por lo que cada variable con una definición en el bucle obtiene un phi allí.flags_2Phi está muerto a la llegada.flagstiene definiciones en ambos predecesores, y la etapa 6 lo elimina porque nada lo lee.

El paso de renombramiento también fija los anchos. edx_1 = 0es una definición de 32 bits y rdx_3 = rdx_2 + 1es de 64 bits, por lo que el phi rdx_2La lectura de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de la letra de%rcxEn elcmpq %rdx, %rcxes de 64 bits mientras que su definición ecx_1 = esi_0es de 32 bits, por lo que el uso se hace zext64(ecx_1)Cada elenco en el pseudocodo final proviene de esta regla.

La parte "lite" es la salida de la SSA.coalesce_webs()Se puede dividir en copias paralelas, donde dos phis en una unen valores de intercambio, y la lección no maneja ese caso. La sección 7 de Cytron describe la salida general a través del colorado.

Etapa 6: Simplificar, sustituir y eliminar el código muerto

optimize()El simplificador reescribe expresiones de abajo hacia arriba con estas reglas:

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

La sustitución hacia adelante se mueve una definición en su único uso. La regla tiene tres condiciones. El uso está en el mismo bloque. El uso es una declaración y no una phi. Cuando la definición contiene una carga, ninguna tienda o llamada se encuentra entre la definición y el uso. Una constante o una dirección de sección se sustituye en cualquier lugar excepto en una phi, porque un argumento phi tiene que permanecer una variable para la web.

La eliminación de código muerto elimina una asignación o una phi cuyo destino no tiene uso y cuya expresión no tiene llamada.percent_changeel resto edx_2 = dividend_1 % edi_0Está muerto, así que va primero.edx_1 = signfill(eax_1)con un solo uso, por lo que se sustituye por dividend_1 = edx_1:eax_1¿ Qué ?

La regla de pareja entonces reduce signfill(eax_1):eax_1¿ Qué ?eax_1, y la división se convierte eneax_1 / edi_0Ahora .eax_1 = esi_1 * 100tiene un solo uso y esi_1 = esi_0 - edi_0El uso de los datos de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información de la información.flagsLas definiciones se han sustituido en las condiciones de rama y desaparecen.

creturn (param_2 - param_1) * 100 / param_1;

El multiplicador 0x66666667

gradeDivide la puntuación por 10, y el clang emite no idiv- Emite .imulq $0x66666667, %rax, %rax, entoncessarq $0x22, %rcxen una copia, entonces shrq $0x3f, %raxGranlund y Montgomery, "División por enteros invariantes utilizando la multiplicación" (PLDI 1994), describen el método.my un turno .s- Por cada uno .xen el rango, (x * m) >> sdebe ser igual x / d¿ Qué ?

Aquí tienes .m = 0x66666667 = 1717986919y s = 34La aritmética se confirma:2^34 / 10 = 1717986918.4, y el techo es1717986919El error esm * 10 - 2^34 = 6Para que la identidad se mantenga en cada 32 bits .x, los tiempos de error 2^31Debe permanecer por debajo.2^34, por lo que el error debe ser al máximo 8.(x * m) >> 34igual floor(x / 10)por cada uno de los no negativos int¿ Qué ?

Para negativo xEl piso se aleja de cero mientras que C se dirige hacia cero. shrq $0x3fextrae el bit de signo del producto, que es 1 exactamente cuando xSi se añade correcta el redondeo.

magic_divisor(m, s)prueba un par de candidatos.dcomo el cuotiente entero de 2^spor my trata dedy d + 1. acepta unadSólo cuandomes igual al límite máximo de 2^s / dy el error de la unión se mantiene.s = 33da .d = 5, lo cual es correcto, porque 0x66666667es también el techo de 2^33 / 5Una prueba verifica la identidad contrac_div(x, 10)En los últimos años, el número de puntos de referencia de laint- El rango.

Con el reconocimiento en,gradeLeíaecx_1 = param_1 / 10 - 6;Con el reconocimiento apagado, la demo imprime la forma en bruto:

crax_1 = (long)param_1 * 0x66666667;
ecx_1 = (int)(rax_1 >> 34) + (int)((unsigned long)rax_1 >> 63) - 6;

Cuando se lee el código de un compilador que no ha estudiado, la forma en bruto es la honesta.

Etapa 7: Tipos de uso

El código de máquina tiene anchos y no tipos. infer_types()Una red cuyas definiciones son de 32 bits de ancho esint, y una web de 64 bits es longUna red que es la base de un acceso de elementosbase[idx]con elementos de 4 bytes es int *El tipo de puntero se extiende a lo largo de copias y fichas, así que local_8 = param_1En el-O0hace a ambos .int *. El tipo de devolución es el ancho del valor en el returnlas declaraciones.

El resultado para total¿ Es verdad ?int total(int *param_1, int param_2)Ghidra escribióint total(long param_1,uint param_2)El levantador usó la carga escalada como evidencia de la señal.*(int *)(param_1 + uVar2 * 4)- Ni siquiera puede ver .const, y ninguno puede decir .int *de launsigned *¿ Qué ?

Lee, Avgerinos y Brumley (TIE, NDSS 2011) recopilar el tipo de marco como resolución de restricciones sobre una red. El resultado es un límite superior e inferior en lugar de una conjetura. Noonan, Loginov y Cok (Retypd, PLDI 2016) añaden polimorfismo y recursión.constEn esta lección se utilizan las dos reglas más baratas de esa línea de trabajo y se nombran lo que no pueden ver.

La aritmética sin firmar se mantiene en las expresiones y no en los tipos de variables. jadespués decmpl $0x4, %ecxImágenes como (unsigned int)ecx_1 > 4, y shrqImágenes como (unsigned long)rax_1 >> 63Las variables permanecen firmadas para que el lector vea el valor del registro y el único lugar donde la instrucción la trata como sin firmar.

Etapa 8: La estructuración a partir del gráfico

Cifuentes (1994), capítulo 6, coincide con las regiones del gráfico en comparación con las formas de un lenguaje de alto nivel. Los bucles vienen primero (6.6.1), luego los condicionals bidireccionales (6.6.2), luego los condicionals n-direccionales (6.6.3). Yakdan, Eschweiler, Gerhards-Padilla y Smith (NDSS 2015) toman el otro camino. Derivan la condición bajo la cual cada nodo ejecuta y construyen la estructura a partir de esas condiciones, sin formas fijas. Su motivación es una medida. Hex-Rays en una muestra producida 1.571 gotoLas declaraciones en 49.514 líneas, una por 32 líneas, y su estructuración no produjo ninguna de las 1.530 funciones de núcleo.

structure()Es un miembro pequeño de la primera familia. Necesita dos hechos de la etapa 2.BranchEl borde trasero es un borde de un bloque que domina su fuente. El bucle natural es el conjunto de bloques que llegan a la fuente del borde trasero sin pasar el encabezado.

Por unBranchcon unirse j¿Qué es esto ?

  • Si ...jes el objetivo de salto, emitirif (!cond) { fallthrough region }y continuar en j¿ Qué ?
  • Si ...jes el paso de caída, emitirif (cond) { target region }y continuar en j¿ Qué ?
  • Si ...jes la salida de función, ambos brazos regresan.if (cond) {... }y el otro brazo después de él, sin un elseEste es el regreso temprano enpercent_changey total¿ Qué ?
  • De lo contrario emitirif (cond) { target } else { fallthrough }y continuar en j- Esto es ...percent_changeEn el-O0, donde ambas armas se almacenan .local_4y nos vemos en el epílogo.

Para un encabezado de bucle:

  • Si el encabezado termina en un Branchcon un sucesor fuera del bucle, emitir while (cond) { body }- ¿ Qué ?totalEn el-O0tiene esta forma: el encabezado compara local_14conlocal_cy las hojas en jge¿ Qué ?
  • Si el encabezado es también el bloque con el borde trasero, o el bloqueo sale en su lugar, emitir do { body } while (cond);- ¿ Qué ?totalEn el-O1tiene esta forma: bloque 0x50añade, incrementa, compara y se vuelve a sí mismo.

Un IndirectJumptermina un bloque sin sucesores.gradeEn el-O0Es una verdadera mesa de saltos.jmpq *%raxa través de cinco compensaciones relativas en .rodata+0x0En esta ocasión, el levantador imprime una nota.0x76¿ Qué ?0x7f¿ Qué ?0x88, y 0x91Los resultados de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la investigación de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia de la ciencia deswitch¿ Qué ?

¿Qué hizo -O1 con el interruptor?

En el-O1Clang no guardó una mesa de salto para grade- Se calcula .score / 10 - 6y comparó el resultado con 4 como un número sin firmar. Luego cargó el valor de retorno de una tabla de cinco entradas de intEn el.rodata+0x14La comparación sin firmar es una instrucción que cubre ambas cosas .score / 10 < 6y score / 10 > 10Una diferencia negativa es un valor sin firmar grande, por lo que no se realiza el mismo cheque.

La mesa se mantiene .1, 2, 3, 4, 4para los cuotientes 6 a 10.switchcon cinco returnlas declaraciones se convirtieron en un ifEl elevador emite exactamente eso, y llama la mesa.lookup_14después de su compensación de sección.

La longitud de la tabla es heurística.table_values()Leer de la mesa de inicio a la siguiente .rodatacompensa que cualquier .textEn este objeto, el siguiente desplazamiento referido es 0x28, la tabla de grade_label, así quelookup_14¿ Qué ?(0x28 - 0x14) / 4 = 5La comparación sin firmar contra 4 está de acuerdo. Ghidra tampoco sabe la longitud.*(uint *)(&DAT_001000b4 + (ulong)uVar2 * 4), una dirección de byte más un offset, y dejó los cinco valores para que el lector busque.

Junto con Ghidra

La demostración imprime cada par.code/fixtures/ghidra/*.json, los registros de la evidencia que rea regresó el 2026-10-14.npx -y rea-agents@6.5.0 decompile ledger-x86_64-O1.o <function> --provider ghidra --format jsonCada disco llevaconfidence: observedy authority: shipped-artifactEl texto se observa. Las afirmaciones dentro del texto se derivan, y los registros no dicen cuáles.

%_cambio

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.

La versión reparada en code/fixtures/ghidra/ledger-x86_64-O1.ghidra-repaired.cLa prueba de la fuente de entrada de 81 pares de datos.

grado

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.

total

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.

El examen de comportamiento recopila el texto de Ghidra para gradey totalNo se ha cambiado.typedeflíneas y una definición de DAT_001000b4desde los veinte bytes en .rodata+0x14Ambas funciones coinciden con la fuente en la red.

Lo que dicen las investigaciones sobre la lectura de Pseudocodo

LLM4Decompile (EMNLP 2024) midió Ghidra solo en una re-executibilidad del 20,12 por ciento en HumanEval-Decompile. Alrededor de una de cada cinco funciones recopiló y aprobó las afirmaciones que vienen con el índice de referencia. Al refinar la producción de Ghidra con su modelo 6.7B aumentó eso al 52.74 por ciento. DecompileBench (Findings of ACL 2025) ejecutó seis decompiladores industriales y seis enfoques de LLM en 23,400 funciones de 130 programas. Los resultados del LLM se leeron mejor y tenían una corrección de funcionalidad 52,2% menor.

Liu, Raff y Micinski (arXiv 2609.05370, septiembre 2026) explican la brecha. Utilizaron 300 funciones de biblioteca de GitHub y 287 funciones basadas en CVE. El modelo de refinamiento más fuerte aumentó la tasa de construcción de Ghidra de 75 a 90 por ciento mientras que la tasa de coincidencia de comportamiento cayó de 74 a 62 por ciento. Los candidatos que pasaron todas las pruebas en el índice de referencia todavía divergieron en las entradas despejadas. Eso ocurrió en el 4,9 por ciento de los casos en ocho sistemas, y en hasta el 13 por ciento para un sistema. Hasta una décima parte de las vulnerabilidades reveladas desaparecieron del código refinado.

Las causas que rastrean son simples: campos inventados, tipos, calles y guardias reemplazaron las incógnitas visibles que el decompiler había dejado en el texto.

Esta es la razón por la que el elevador imprime .(unsigned long)(unsigned int)ecx_1Imprime una nota en lugar de un nombre más bonito.switchpara el salto no resuelto, y lookup_14En lugar de una etiqueta que un modelo inventaría.code/main.py- El de Ghidra.undefined1 [16]Es feo y honesto.int percent_change(int, int)El mismo modelo producirá una firma equivocada en la siguiente función.

Construye el mismo

Ejecutar la demostración:

bashcd phases/13-tools-and-protocols/34-lifting-to-pseudocode
python3 code/main.py

La demostración decodifica las cuatro funciones de la-O1Objeto y compara cada instrucción con la cometida llvm-objdump¿Qué es lo que se dice?percent_change¿ Qué ?grade, y totalImprime el IR antes de SSA, la lista SSA con funciones phi y el pseudocodo al lado del texto Ghidra.gradecon el reconocimiento de división apagado, el -O0versiones de percent_change¿ Qué ?total, y grade_label, y un resumen:

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
}

Leer .code/main.pyen este orden:

  1. read_object()se lee los encabezados de la sección, la tabla de símbolos y el .relasecciones de un objeto relocable ELF64.
  2. decode_one()y render_att()Descifrar una instrucción y imprimirlo en el camino llvm-objdump- Sí, lo hace.
  3. build_blocks()¿ Qué ?immediate_dominators(), y dominance_frontiers()construye los hechos gráficos.
  4. lift_insn()convertir una instrucción en declaraciones de IR y resolver las reubicaciones.
  5. insert_phis()y rename()construir SSA e insertar lanzamientos de ancho.
  6. make_simplifier()¿ Qué ?substitute_once(), y remove_dead_code()Corren hacia adentro .optimize()¿ Qué ?
  7. collect_params()¿ Qué ?coalesce_webs(), y infer_types()nombre y escribir las redes.
  8. structure()conpost_dominators()y natural_loops()emite el cuerpo C.
  9. lift_function()se unen las etapas y lift_all()Levanta un objeto entero con firmas de llamadas.

Usalo

Para levantar una función de su propio objeto, construye con clang -target x86_64-unknown-linux-gnu -cy llamarlift_all(read_object(path))Cuando el decodificador se elevaDecodeError, lee el byte de código optico del mensaje y encuentra en el mapa de código optico SDM. Luego añada una entrada a las tablas en la parte superior del archivo. Cuando el elevador eleva en un mnemónico, añada una regla a lift_insn()Cuando el constructor emite ungoto, imprimir el gráfico con IrPrinterCada adición es pequeña y cada una se comproba conllvm-objdumpo el clang por los ensayos existentes.

Cuando se instale Ghidra, ejecuta rea en la misma función y coloca los dos textos uno al lado del otro. Llena la tabla de diferencia antes de creer cualquiera de los textos. Una diferencia con una instrucción detrás de ella es una elección de presentación. Una diferencia sin instrucción detrás de ella es una suposición, y la suposición puede ser de ambos lados.

Laboratorio interactivo

Laboratorio A: una función a través de cinco etapas

Desde elcodedirectorio, Python abierto:

bashcd phases/13-tools-and-protocols/34-lifting-to-pseudocode/code
python3 -q
  • ¿Qué quieres decir ?
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)

Sigue .%eside lasubl %edi, %esi¿ Qué ?esi_1 = esi_0 - edi_0¿ Qué ?(param_2 - param_1)El byte .fees el ModR/M de la subl, con mod = 11¿ Qué ?reg = edi, y r/m = esi¿ Qué ?

Laboratorio B: el lenguaje de división

pythonprint(lift_function(obj, "grade", recognize_division=False).pseudocode)
print(magic_divisor(0x66666667, 34), magic_divisor(0x66666667, 33), magic_divisor(0x66666667, 31))

La primera línea imprime el multiplicador bruto y los cambios.10 5 NoneDescubra por qué el mismo multiplicador se divide por 5 con un cambio menos, y por qué el cambio 31 no cumple con la línea de error.

Laboratorio C: un nuevo objeto detiene el decodificador

Construir el dispositivo en -O2y levantarlo:

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"))'

El decodificador se detiene en 0x56conopcode 81 is outside the fixture subset- Código de opciones .81Es el formulario de grupo 1 con una inmediata de 32 bits.andl $0x7ffffff8, %edx, y Clang lo usó en el bucle vectorizado de total- Añade a la parte de la parte .0x83En eldecode_one()El subconjunto crece una entrada a la vez, y cada entrada se comproba con llvm-objdump¿ Qué ?

Laboratorio D: Valor predeterminado de Ghidra en grado

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)

Cada fila imprime 0, Ghidra uVar1La expresión es correcta e ilegible, y la instrucción xorl %eax, %eaxlo dice en dos bytes.

Laboratorio de práctica

Añadirswitchrecuperación para gradeEn el-O0¿ Qué ?

Requisitos:

  • Lea las cinco reubicaciones en .rela.rodatade ledger-x86_64-O0.o, cada uno R_X86_64_PC32.text+offset, y calcular los cinco objetivos del caso desde la tabla en .rodata+0x0¿ Qué ?
  • Añadir unSwitchterminador en la IR con la expresión de índice, la lista de objetivos y el valor predeterminado de la jaantes de la carga de la mesa.
  • Dale al bloque de interruptor un sucesor por objetivo para que los dominadores, posdominadores y SSA sigan trabajando.
  • Emiteswitch (local_10) { case 0:... }con uno casepor objetivo y el epílogo compartido después de la unión.
  • Guarde la nota para cualquier salto indirecto cuya mesa no pueda atar.
  • Compile el resultado con clang y compare con la fuente sobre la cuadrícula de puntuación en las pruebas.

El éxito significagradeEn el-O0y gradeEn el-O1producen la misma salida en la red, con una switchY no .goto¿ Qué ?

Artículo

Esta lección produceoutputs/skill-lifting-to-pseudocode.md. Utilice para levantar una función con una derivación para cada línea y para llenar la tabla de diferencia con el texto de un descompilador. El artefacto requiere el desmonte de referencia, las reubicaciones, la convención de llamada, la composición de composición, la comparación de comportamiento y un veredicto para cada línea diferente.

Verifique el hecho

Ejecutar la demostración y la suite determinista:

bashcd phases/13-tools-and-protocols/34-lifting-to-pseudocode
python3 code/main.py
python3 -m unittest discover -s code/tests -v

La suite tiene 25 pruebas.

  • el decodificador está de acuerdo con el compromiso llvm-objdumptexto en cada instrucción de ambos objetos, y con un vivo llvm-objdumpcuando se instala
  • REX, ModR/M, SIB y el desplazamiento de campo de compensación de leaq (%rip), %rcxse decodifican como se describe en el MDS
  • un código op fuera del subconjunto aumenta DecodeErrorEn vez de adivinar
  • El lector de ELF enumera las cuatro funciones y las tres .textreubicaciones
  • totaltiene cinco cuadras,0x44domina .0x50, y 0x50es un encabezado de bucle con sí mismo como el cuerpo
  • %rdiy %rsi¿ Cómo se hace ?param_1y param_2, y param_1En eltotal¿ Es verdad ?int *
  • testy cmpconje¿ Qué ?jle¿ Qué ?ja, y jge¿ Cómo se hace ?==¿ Qué ?<=, sin firmar .>, y <
  • SSA coloca dos funciones Phi en vivo en el encabezado de bucle de totaly ninguno en .percent_change
  • el pseudocódigo de percent_change¿ Qué ?grade, y totaligual al texto esperado
  • gradeLeíaparam_1 / 10 - 6con reconocimiento en y el bruto multiplicarse con reconocimiento fuera
  • 0x66666667con el turno 34 dividido por 10, con el turno 33 dividido por 5, y la identidad se mantiene en la cuadrícula
  • las ranuras de pila en -O0¿ Cómo se hace ?local_4¿ Qué ?local_8¿ Qué ?local_c, y el marcador de marco nunca aparece
  • la mesa en .rodata+0x14Leía1, 2, 3, 4, 4a través de la reubicación
  • el salto indirecto engradeEn el-O0se informa con sus cuatro bloques sin levantar
  • Las lámparas de Ghidra contienen el retorno de 16 bytes, el param_3, el no simplificado .xor, y el long param_1
  • las seis unidades emitidas se compilan con clang -std=c11 -Wall -Werrorporx86_64-unknown-linux-gnu
  • las funciones levantadas, el texto reparado de Ghidra, y la fuente imprimen las mismas 232 líneas sobre la cuadrícula de entrada

Modos de falla de producción

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

Regla de funcionamiento

Trate el pseudocodo como una derivación que no ha visto. Reproduce a partir de las instrucciones con un elevador que puede leer. Coloque los dos textos uno al lado del otro, y escriba la instrucción o la adivinación detrás de cada línea que difiere. Compile ambas y ejecute ambas contra la fuente cuando lo tengas. Una línea sin instrucción detrás de ella permanece como una adivinación sin importar cuán limpia lea.

Leer más

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.