Phase 13: Tools & Protocols

Recuperación de datos y tipos: estructuras, matrices, objetos de C++ y DWARF

Un descompilador imprimea->balancecuando tiene una tabla de tipo y *(int *)(param_1 + 4)Esta lección construye la tabla a partir de los accesos, lee la verdadera de DWARF 5, y muestra dónde están de acuerdo los dos.

Type: Build

Languages: Python, C, C++, x86-64 assembly

Prerequisites: Phase 13 · 33 (disassembly and control flow), Phase 13 · 34 (lifting to pseudocode)

Time: ~140 minutes

Objetivos de aprendizaje

  • Lea un acceso de miembro de estructura como registro base más desplazamiento, y un acceso de matriz como base más índice veces paso, de las reglas de dirección de Intel SDM.
  • Aplicar las reglas de alineación del Sistema V a la mano a struct accounty explicar cada byte de relleno.
  • Construye un pase de recuperación de tipo que convierta los accesos en una lista en offsets de campo, anchos, tipos, un paso y un tamaño, con la evidencia para cada uno.
  • Escriba un lector DWARF 5 para .debug_info¿ Qué ?.debug_abbrev¿ Qué ?.debug_str_offsets, y .debug_addrde un objeto relocable, y comprobarlo con llvm-dwarfdump¿ Qué ?
  • Comparar el diseño recuperado con el diseño DWARF y nombrar cada diferencia como evidencia faltante, un acceso fusionado, o un tipo no comprobable.
  • Lea un vtable de Itanium C++, su tipo de información y sus nombres desfigurados de las reubicaciones, y resuelve una llamada virtual a una ranura.

El problema

Usted compiladoaccount.cSin-gy preguntó a un agente qué.bumpEl agente envió el objeto a Ghidra 12.1.4 y recibió esto:

c
void bump(long param_1,int param_2)
{
  *(int *)(param_1 + 4) = param_2 + *(int *)(param_1 + 4);
  return;
}

La misma fuente compilada con -gda esto desde el mismo decompiler:

c
int bump(account *a,int by)
{
  a->balance = by + a->balance;
  return a->balance;
}

Las instrucciones son idénticas en ambos objetos. La diferencia es una tabla de tipo. Con la tabla el descompilador sabe que el primer parámetro apunta a una estructura de 32 bytes cuyo campo en el offset 4 es un 4 byte intnombrebalanceSin él , el decompiler sólo sabe que algo de 8 bytes de ancho llegó .%rdiy que 4 bytes en +4se leían y se escribían.

Todo en esa tabla tiene una fuente.addl 0x4(%rax), %ecxEl ancho 4 proviene de lalEl nombre proviene de unDW_TAG_memberEntrada en .debug_infoEsta lección construye ambas mitades: un pase que recupera lo que las instrucciones demuestran y un lector que decodifica lo que el compilador grabó.

Un acceso de miembro es un desplazamiento

El volumen 1 del SDM de Intel, sección 3.7.5, define la dirección efectiva como Offset = Base + (Index * Scale) + Displacement, con una escala de 1, 2, 4 o 8 y un desplazamiento de 8, 16 o 32 bits.RIP + Displacement. Un acceso a miembros de estructuras utiliza la primera forma sin índice: el registro base contiene la dirección del objeto y el desplazamiento es el desplazamiento de miembros.

¿ Qué ?bumpEn el-O0. La lista de llvm-objdump -d -rSe dice:

output
       4: 48 89 7d f8                  	movq	%rdi, -0x8(%rbp)
       e: 48 8b 45 f8                  	movq	-0x8(%rbp), %rax
      12: 03 48 04                     	addl	0x4(%rax), %ecx
      15: 89 48 04                     	movl	%ecx, 0x4(%rax)

Descifrar03 48 04con las reglas de la lección 33. 03¿ Es verdad ?ADD r32, r/m32- ModRM .48¿ Es verdad ?01 001 000¿ Qué es esto ?mod = 01se refiere a un operando de memoria con un desplazamiento de 8 bits, reg = 001¿ Es verdad ?%ecx¿ Qué ?r/m = 000¿ Es verdad ?%raxEl byte de desplazamiento es 04La instrucción lee 4 bytes en %rax + 4y los agrega a %ecx- El .lel sufijo que llvm-objdumpImpresiones es el tamaño de la operanda, así que el campo es de 4 bytes de ancho.%raxvino de la ranura del marco en -0x8(%rbp), y ese espacio recibido%rdi, el primer argumento de un número entero bajo la convención de llamadas del Sistema V. Sigue tres hechos: el primer parámetro es un puntero, el objeto al que apunta tiene un campo de 4 bytes en el offset 4, y bumplee y escribe ese campo.

Nada en estos bytes diceint¿ Qué ?balance, ostruct accountNo hay nada que diga cuánto es el objeto. Los 28 bytes restantes de la estructura son invisibles para elbump¿ Qué ?

Las reglas de diseño del sistema V

El x86-64 psABI, sección 3.1.2, da tres reglas para los agregados. "Las estructuras y las uniones asumen la alineación de su componente más estrictamente alineado. Cada miembro se asigna al offset disponible más bajo con la alineación apropiada. El tamaño de cualquier objeto es siempre un múltiplo de la alineación del objeto".char1 y 1, short2 y 2, int4 y 4, double8 y 8, cualquier puntero 8 y 8.

Aplicar las normas a las estructuras enaccount.c¿Qué es esto ?

c
struct account {
    char kind;
    int balance;
    short code;
    double rate;
    const char *label;
};
MemberSizeAlignmentLowest offset that fitsPadding before
kind1100
balance4443 (bytes 1 to 3)
code2280
rate88166 (bytes 10 to 15)
label88240

El último miembro termina en 32. La alineación más estricta es 8, y 32 es un múltiplo de 8, por lo que el tamaño es 32 sin relleno de cola. Nueve bytes de los 32 son relleno, y el psABI dice "El contenido de cualquier relleno es indefinido".

El orden de los miembros determina el tamaño.double, pointer, int, short, charel relleno compensa 0, 8, 16, 20, 22 y termina en 23, que ronda hasta 24. sysv_layout()En elcode/main.pyaplica las tres reglas y produce ambos resultados.ctypesel módulo aplica las mismas reglas a través de la convención del compilador host, y ctypes.sizeof¿ Qué ?ctypes.alignment, y el .offsetde cada campo dar 32, 8, y 0, 4, 8, 16, 24El programa mismo imprime la tercera copia cuando se construye y se ejecuta en esta máquina:

output
sizeof 32 align 8
kind 0 balance 4 code 8 rate 16 label 24
sum 315 tag -3 projected 318.75 label_length 7

La arm AAPCS64 establece las mismas reglas agregadas en su sección 5.10.1, y el documento arm64 de Apple mantiene estos cinco tamaños escalares, por lo que los números coinciden en los dos objetivos.-target x86_64-unknown-linux-gnupara que el lector ELF de la lección 33 y el lector DWARF de abajo trabajen en ellos.

Lea el resto de la estructura a mano

bumpVe un campo. Las otras cuatro funciones en account.cEn el caso de las direcciones de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de la dirección de laaccount-x86_64-O0-g.objdump.txt¿ Qué ?

sum_balancesindexa una matriz de la estructura:

output
      51: 48 8b 45 f8                  	movq	-0x8(%rbp), %rax
      55: 48 63 4d ec                  	movslq	-0x14(%rbp), %rcx
      59: 48 c1 e1 05                  	shlq	$0x5, %rcx
      5d: 48 01 c8                     	addq	%rcx, %rax
      60: 8b 40 04                     	movl	0x4(%rax), %eax

%rax¿ Es verdad ?list¿ Qué ?%rcx¿ Es verdad ?ide signo extendido a 64 bits, y shlq $0x5y la multiplicamos por 32.addq¿ Qué ?%rax¿ Es verdad ?list + 32 * i, y la carga lee 4 bytes en el desplazamiento 4 del elemento i. El conteo de cambios es el único lugar en este objeto donde el tamaño de la estructura aparece como un número.0x49a la jmp 0x49En el0x72¿ Qué ?

tagSe lee dos campos estrechos:

output
      8c: 0f be 00                     	movsbl	(%rax), %eax
      98: 0f bf 48 08                  	movswl	0x8(%rax), %ecx

movsblcarga un byte en el desplazamiento 0 y se extiende de signo, por lo que el campo en 0 es un número entero de 1 byte firmado. movswlcarga dos bytes en el offset 8 con extensión de signo, por lo que el campo en 8 es un número entero de 2 bytes firmado.-O1El primer acceso se hace cmpb $0x6c, (%rdi), que todavía dice 1 byte en el desplazamiento 0 y no dice nada sobre el signo.

projecteddice un flotante:

output
      cc: f2 0f 2a 40 04               	cvtsi2sdl	0x4(%rax), %xmm0
      d5: f2 0f 59 40 10               	mulsd	0x10(%rax), %xmm0

cvtsi2sdlConverte un número entero de 32 bits firmado de la memoria a un doble, lo que confirma que el offset 4 tiene un número entero de 4 bytes firmado. mulsdse multiplica por el doble de la memoria, por lo que el offset 16 tiene un valor de punto flotante de 8 bytes.

label_lengthlee un puntero y luego lo sigue:

output
      f3: 48 8b 40 18                  	movq	0x18(%rax), %rax
      f7: 48 63 4d f4                  	movslq	-0xc(%rbp), %rcx
      fb: 80 3c 08 00                  	cmpb	$0x0, (%rax,%rcx)

El movqSe lee 8 bytes en el offset 24. Por sí solo que es un integer de 8 bytes o un puntero.cmpbEl valor de la memoria que se utiliza como dirección es un puntero.cmpbse lee 1 byte en el índice %rcxde la misma, y en -O1el puntero está avanzado con leaq 0x1(%rcx), %rcxdentro del bucle, por lo que los elementos apuntados están a 1 byte de distancia.

Si juntamos las cinco funciones, la estructura es{ i8 at 0, i32 at 4, i16 at 8, f64 at 16, pointer at 24 }Las diferencias entre 1 y 3 y 10 y 15 son exactamente el relleno previsto por las reglas de psABI.const char *, el tipo declarado del byte firmado en 0, que la fuente escribe char, y que las cinco funciones reciben el mismo tipo.main, que pasa un objeto de marco a los cinco.-O0Las páginas de llamadas hacen que sea visible: leaq -0x70(%rbp), %rdiseguido por addq $0x20, %rdiantes decallq bump, y addq $0x40, %rdiantes decallq tagLos desventajos son 0,0x20, y 0x40son tres elementos de una matriz, y el memcpyde 0x60los bytes que lo llenan es 96 bytes, que es tres elementos de 32.

El pase

trace_function()En elcode/main.pyEl control de la función de un registro de 64 bits o de un espacio de marco como -0x8(%rbp)Cada titular tiene uno de los tres valores.Pointeres una raíz más una offset constante más un conjunto de pasos, donde un paso registra que un múltiple desconocido de tantos bytes se agregó.Indexes un número entero de valor desconocido que es un múltiplo de una escala.

El estado comienza con los seis registros de argumentos del Sistema V .Pointer("param0", 0)por elPointer("param5", 0)Entonces cada instrucción aplica una regla:

  • ¿ Qué es esto ?movqUna escritura de 32 bits o más estrecha en un registro lo hace un Indexcon escala 1.
  • ¿ Qué es esto ?leaqcon una base de puntero produce un puntero con el desplazamiento añadido, y con un registro de índices añade scale * index_scaleA los pasos.leaqcuya base es %rbpo %rspnombra un objeto de marco, que es cómo mainPases rows¿ Qué ?
  • shlq $nen unIndexmultiplica su escala por 2^n, y imulq $kse multiplica por k¿ Qué ?
  • addqde un puntero e índice mantiene el puntero y añade la escala del índice a los pasos. addq $ka un indicador se mueve su descenso y se registra también como incremento de dicho registro.
  • Una llamada hace que cada registro guardado por el llamador sea desconocido, y los registros de argumentos que contienen punteros se registran como un sitio de llamada.
  • Cualquier operando de memoria cuyo registro base contiene un puntero se convierte en un Accesscon la raíz y el desplazamiento del puntero, el ancho y el tipo de la mnemónica, y una bandera de la tienda.

La amplitud y la bondad vienen del mnemónico solo.access_width()mapas de la b¿ Qué ?w¿ Qué ?l, y qlos sufijos a 1, 2, 4 y 8 bytes, movsbly movswlpara las cargas estrechas firmadas, movzbla los no firmados, el sdy ssinstrucciones de SSE escalar para flotadores de 8 y 4 bytes, cvtsi2sdla una fuente de un número entero de 4 bytes firmada, y el de 16 bytes movapsfamilia a un vector. leaqy el noplos formularios no son accesos.

python
if memory.base in FRAME_REGISTERS:
    return Pointer(f"frame({memory.base}{format_signed(memory.displacement) if memory.displacement else ''})", 0)
base = env.get(memory.base)
if not isinstance(base, Pointer):
    return Unknown
strides = set(base.strides)
if memory.index is not None:
    index_value = env.get(memory.index)
    strides.add((index_value.scale if isinstance(index_value, Index) else 1) * memory.scale)
return Pointer(base.root, base.offset + memory.displacement, frozenset(strides), base.origin)

Una carga de 8 bytes a través de un puntero produce un nuevo puntero cuyos nombres raíces el campo de que proviene, como param0->0x18. Cuando ese valor se utiliza más tarde como base, o avanzado con leaq, el campo de que viene se marca como un puntero probado. Esa es la regla que convierte el descalzo 24 de "8 bytes" en "puntero".

Cuatro tipos de pruebas de pasos

infer_layout()fusiona los accesos de cada función de testigo y recoge el paso de cuatro fuentes, cada una con la instrucción que lo produjo.

KindWhere it comes fromBuild that shows it
scalean Index with scale above 1 reached a pointer, from shlq $0x5 or imulq-O0, shlq $0x5, %rcx at 0x59
loop-stepa 64-bit increment of the base or index register inside a loop that also contains the access-O1, addq $0x20, %rdx at 0x24
offset-deltasame-width accesses through one base register inside one loop, at offsets a constant apart-O2, eight loads 32 apart
call-sitethe offsets at which one frame object is passed to the witness functions-O0, main passes rows, rows+0x20, rows+0x40

Un bucle es un retroceso .jmpEn el caso de los datos de acceso, el incremento se calcula sólo cuando el rango de bucle contiene tanto el incremento como el acceso.addl $0x1, %eaxque avanza.iEn el-O0, porque es un 32 bits de adición y porque el acceso no pasa por %eaxEl paso final es el mayor divisor común de todos los valores encontrados. El tamaño es el paso cuando el paso es al menos tan grande como el final del campo final.

El bucle vectorizado en -O2

En el-O2Clang vectorizado sum_balances. El cuerpo del bucle de0x50¿ Qué ?0xaaSe lee ocho elementos por pase:

output
      3b: 48 8d 87 e4 00 00 00         	leaq	0xe4(%rdi), %rax
      50: 66 0f 6e 50 80               	movd	-0x80(%rax), %xmm2
      55: 66 0f 6e 98 60 ff ff ff      	movd	-0xa0(%rax), %xmm3
      61: 66 0f 6e 90 40 ff ff ff      	movd	-0xc0(%rax), %xmm2
      69: 66 0f 6e a0 20 ff ff ff      	movd	-0xe0(%rax), %xmm4
      7d: 66 0f 6e 10                  	movd	(%rax), %xmm2
      81: 66 0f 6e 58 e0               	movd	-0x20(%rax), %xmm3
      8a: 66 0f 6e 50 c0               	movd	-0x40(%rax), %xmm2
      8f: 66 0f 6e 60 a0               	movd	-0x60(%rax), %xmm4
      a0: 48 05 00 01 00 00            	addq	$0x100, %rax
      aa: 75 a4                        	jne	0x50 <sum_balances+0x40>

%raxcomienza enlist + 0xe4Los ocho desplazamientos colocan las cargas en compensaciones .0x04, 0x24, 0x44, 0x64, 0x84, 0xa4, 0xc4, 0xe4de lalist, que son4, 36, 68, 100, 132, 164, 196, 228El incremento es0x100Un pase que registra compensaciones al verlas informa ocho campos y una estructura de al menos 232 bytes.

fold_unrolled()En un bucle, toma los accesos de la misma anchura a través de un registro base. Sus compensaciones sondaparte, aquí 32. Si el paso del bucle es k * dY hay exactamentekLos cambios de la velocidad de la velocidad de los motores de carga son distintos, el bucle se desarrolla.kveces, y cada acceso es el mismo elemento de acceso en el offset (offset - lowest) mod dAquí tienes .256 = 8 * 32con ocho desplazamientos, así que todas las ocho cargas se doblan en el desplazamiento 4, y los registros de pruebas de pasooffset-delta 32¿ Qué ?unrolled 32, y loop-step 256El ciclo de residuos escalares en 0xe0añade scale 32de lashlq $0x5, %rdxy loop-step 32de laaddq $0x20, %rdxEl divisor común más grande de los cinco valores es 32.

La figura tiene un botón que desactiva el plegado.-O2Este estado es lo que un lector ve que confía en el desplazamiento sin el bucle.

DWARF 5 Desde los bytes

El -gconstrucción de account.ccontiene ocho secciones de desactivación. .debug_infoes 493 bytes, .debug_abbrev179, .debug_str230, .debug_str_offsets124, .debug_addrEl número 112,.debug_line312, .debug_line_str12, y .debug_frame264. La introducción de las listas de normas DWARF 5 .debug_str_offsets¿ Qué ?.debug_addr, y .debug_line_strcomo nuevo en la Versión 5. El lector en code/main.pyNecesita los primeros cinco.

El encabezado de la unidad

La sección 7.5.1.1 de la norma da el título completo de la unidad de compilación: unit_length¿ Qué ?version¿ Qué ?unit_type¿ Qué ?address_size, y debug_abbrev_offsetLos primeros doce bytes de .debug_infoson:

output
e9 01 00 00   unit_length = 0x1e9 = 489
05 00         version = 5
01            unit_type = DW_UT_compile
08            address_size = 8
00 00 00 00   debug_abbrev_offset = 0

El unit_typeel byte es nuevo en la versión 5, y el estándar lo dice en la misma sección. La longitud cuenta los bytes después de sí mismo, por lo que la siguiente unidad comenzaría en 4 + 489 = 493, el final de la sección. llvm-dwarfdump --debug-infoImprime los mismos cinco valores en su Compile Unit:la línea, y el primer ensayo analiza esa línea y compara.

Las abreviaturas

La sección 2.1 dice que cada entrada de información de depuración "se compone de una etiqueta de identificación y una serie de atributos". La entrada en sí solo almacena un código de abreviatura..debug_abbrev(sección 7.5.3) que nombra la etiqueta, dice si los niños siguen, y enumera cada atributo con su forma. Este objeto tiene 14 declaraciones. Dos cosas aquí:

output
[12] DW_TAG_structure_type  DW_CHILDREN_yes
     DW_AT_name              DW_FORM_strx1
     DW_AT_byte_size         DW_FORM_data1
     DW_AT_decl_file         DW_FORM_data1
     DW_AT_decl_line         DW_FORM_data1
[13] DW_TAG_member           DW_CHILDREN_no
     DW_AT_name              DW_FORM_strx1
     DW_AT_type              DW_FORM_ref4
     DW_AT_decl_file         DW_FORM_data1
     DW_AT_decl_line         DW_FORM_data1
     DW_AT_data_member_location  DW_FORM_data1

parse_abbreviations()Se lee código ULEB128, etiqueta ULEB128, un byte de niños, luego atributo y forma pares hasta que un 0, 0En la sección 7.5.6 se dan los códigos de formulario: data1es un byte, ref4es un descenso de 4 bytes en relación con la unidad, strx1es un índice de 1 byte en la tabla de compensaciones de cadenas, sec_offsety data4son 4 bytes, addrxes un índice ULEB128 en la tabla de direcciones, exprloces una longitud ULEB128 y que muchos bytes, y flag_presentno tiene bytes en absoluto.

Descifrar el miembro del balance a mano

La entrada de la estructura para accountcomienza en el offset 0x195con los bytes 0c 15 20 00 03El código abreviado es 12, la estructura.DW_AT_namees el índice de cadenas 0x15¿ Qué ?DW_AT_byte_size¿ Es verdad ?0x20= 32, DW_AT_decl_filees 0, y DW_AT_decl_linees 3, que es la línea de struct account {en la fuente.

La segunda entrada de miembros comienza en 0x1a3con0d 10 88 01 00 00 00 05 04¿Qué es esto ?

  1. 0des la abreviatura 13, un miembro.
  2. 10¿ Es verdad ?DW_AT_name¿ Cómo ?strx1: índice de cuerdas 16.
  3. 88 01 00 00¿ Es verdad ?DW_AT_type¿ Cómo ?ref4: compensación 0x188desde el inicio de la unidad, que es el DW_TAG_base_typeEntrada nombrada int¿ Qué ?
  4. 00¿ Es verdad ?DW_AT_decl_filey 05¿ Es verdad ?DW_AT_decl_line: línea 5 de account.c¿ Qué ?
  5. 04¿ Es verdad ?DW_AT_data_member_location: compensación 4.

En la sección 5.7.6 se dice que una entrada miembro "puede tener un atributo DW_AT_data_member_location o un atributo DW_AT_data_bit_offset" y que una constante entera "es el desplazamiento en bytes desde el principio de la entidad que contiene". Ese byte es el mismo número que el desplazamiento en addl 0x4(%rax), %ecx, y compare_layouts()es la función que pone los dos lados a la vez.

Las cadenas y direcciones pasan a través de dos tablas

El índice de cuerdas 16 no es un descenso en .debug_strEl lector tomaDW_AT_str_offsets_basede la entrada de unidad, que es 8, y lee la entrada de 4 bytes en 8 + 4 * 16En el.debug_str_offsetsEsa entrada contiene el descenso de"balance"En el.debug_strLa misma indirección se aplica a las direcciones.sum_balancescomienza con los bytes 07 07: abreviatura 7, un subprograma, entonces DW_AT_low_pc¿ Cómo ?addrxIndice 7. La entrada de 8 bytes en 8 + 8 * 7En el.debug_addr¿ Es verdad ?0x30, la dirección de la función en .textLas primeras seis entradas de esa tabla son las direcciones de las literalmente de seis cuerdas.

En un objeto relocable ninguna tabla tiene sus valores finales..debug_str_offsetses cero en el archivo, y .rela.debug_str_offsetstiene 29 R_X86_64_32las reubicaciones contra .debug_strcon el real de compensación en el adjunto. .debug_addrtiene 13 R_X86_64_64las reubicaciones contra .texty .rodata.str1.1- ¿ Qué ?.debug_infotiene cuatro reubicaciones propias: la abreviatura de compensación, DW_AT_str_offsets_base¿ Cómo ?.debug_str_offsets+0x8¿ Qué ?DW_AT_stmt_list, y DW_AT_addr_base¿ Cómo ?.debug_addr+0x8- ¿ Qué ?ElfObject.relocated()se aplica S + APara los tipos de 32 bits y 64 bits antes de que el lector analice una sección. Un símbolo de sección tiene valor 0 en un archivo objeto, por lo que el campo recibe el agregado. llvm-dwarfdumpEl índice de resolución de la información de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de datos de la fuente de referencia de la fuente de datos de la fuente de referencia de la fuente de referencia de la fuente de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de referencia de la fuente de referencia de la fuente de referencia de referencia de la fuente de referencia de la fuente de referencia de la fuente de referencia de la fuente de referencia de la fuente de referencia de referencia de la fuente de referencia de la fuente de referencia de la fuente de la fuente de referencia de la fuente de referencia de referencia de la fuente de la fuente de referencia de la fuente de referencia de la fuente de referencia de la fuente de la fuente de referencia de la fuente de referencia de la fuente de la fuente de referencia de la fuente de referencia de la fuente de referencia de la fuente de la fuente de referencia de la fuente de la fuente de referencia de la fuente de la fuente de la fuente de referencia de la fuente de referencia de la fuente de la fuente de referencia de la fuente de la fuente de la fuente de la fuente de referencia de la fuente de referencia de la fuente de la fuente de referencia de la fuente de la fuente de la fuente de referencia de la fuente de la fuente de la fuente de la fuente de referencia de la fuente de la fuente de la fuente de referencia de la fuente de la fuente de referencia de la fuente de la fuente de la fuente de la fuente de referencia de la fuente de la

Tipos, funciones y ubicaciones

DwarfInfo.type_name()sigueDW_AT_typeLas referencias.labelpuntos de los miembros en un DW_TAG_pointer_type, que apunta a una DW_TAG_const_type, que señalan lachartipo base, por lo que el nombre se compone de const char *- ¿ Qué ?type_size()retorno DW_AT_byte_sizepara tipos y estructuras de base, 8 para un puntero, y contar veces el tamaño de un elemento para una matriz. type_kind()reduce el DW_AT_encodingde un tipo base a intcon un signo, o float, y un tipo de puntero para pointer, que es el vocabulario que utiliza la comparación.

DwarfInfo.functions()Se lee seis .DW_TAG_subprogramLas entradas.bumpse ejecuta desde 0x0¿ Qué ?0x21, que es DW_AT_low_pcmás el DW_AT_high_pcSu base de marco esDW_OP_reg6 RBP, y su parámetro atiene la expresión de ubicación 91 78¿ Qué es esto ?DW_OP_fbregcon el valor SLEB128 0x78, que es -8. Ese es el espacio del marco -0x8(%rbp)en el que se almacena la lista %rdien la dirección 0x4La información de descomposición y las instrucciones describen la misma ranura desde dos lados.

Comparar las dos estructuras

compare_layouts()El miembro que no tiene campo es no-evidenceUn campo de un tamaño diferente essize-mismatchUn miembro de puntero cuyo campo nunca fue desreferenciado espointer-unproven. Un tipo que no esté de acuerdo, como un float donde las instrucciones utilizan aritmética de números enteros, es kind-mismatch. Los campos inferidos que comienzan dentro de un miembro o dentro de los relleno son unexpectedEl tamaño compara el paso conDW_AT_byte_size¿ Qué ?

Los números de las tres construcciones, desde python3 code/main.py¿Qué es esto ?

BuildAccesses through the first parameterFieldsStride evidenceMembers matchedSize
-O0115scale 32, call-site 32, call-site 645 of 532, match
-O195loop-step 325 of 532, match
-O2175 after folding, 12 beforeoffset-delta 32, unrolled 32, loop-step 256, scale 32, loop-step 325 of 532, match

Los huecos inferidos son bytes de 1 a 3 y 10 a 15 en cada construcción, y esos son exactamente los bytes de relleno del diseño DWARF. Cada campo inferido se encuentra en un múltiplo de su propio tamaño, que es lo que requieren las reglas de psABI, y natural_alignment_ok()- Eso lo registra.

Añadir las funciones una a la vez y la imagen cambia.bumpsolo el diseño es un campo en el offset 4, el tamaño es desconocido, y el límite inferior es 8. sum_balancesañade el paso. tagañade los desventajas 0 y 8. projectedañade el flotador a 16. label_lengthañade el puntero a 24 y demuestra que es un puntero. mainañade los sitios de llamadas en -O0y no añade nada a -O1y -O2La figura pasa por este orden para cada construcción.

Tres cosas permanecen fuera de alcance.constNo está en las instrucciones.labeles sólo "elementos 1 byte aparte" de la leaq 0x1(%rcx), %rcxen el bucle de cuerdas, que es consistente con chary con unsigned chary _Bool- En el-O1El signo del byte en el desplazamiento 0 es desconocido, porque cmpb $0x6c, (%rdi)Las pruebas de aprobación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios de evaluación de los resultados de los estudios 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 los Estados Unidosint8allí yi8En el-O0, donde movsblEstas son las lecciones de hechos 35 registros como desconocidos.

Objetos de C++

shapes.cppdefine Shapecon un destructor virtual, un puro virtual area(), y un virtual name(), y Rect, que se deriva de Shapey añade dos intEl Itanium C++ ABI rige cómo Clang establece estos en x86-64 Linux.

Nombres equivocados

La sección 5.1.2 del ABI dice que "un nombre se corrompe con prefijos _ZEl objeto tiene 19 símbolos de este tipo._ZNK4Rect4areaEvSe lee como _Z, entoncesNpara un nombre anidado, Kpara una función de miembro const, 4Recty 4areacomo nombres de fuentes con prefijo de longitud, Epara cerrar el nombre anidado, y vpara una lista de parámetros vacíos. llvm-cxxfilthuellasRect::area() const- ¿ Qué ?_Z8describeRK5Shapei¿ Es verdad ?describe(Shape const&, int)¿ Qué es esto ?Res una referencia, Kes const, 5Shapees la clase,i¿ Es verdad ?intLos nombres especiales en la sección 5.1.4 son:_ZTVpara una mesa de trabajo, _ZTIpara información de tipo, y _ZTSpara la cadena de nombres de tipoinfo.C1¿ Qué ?C2¿ Qué ?D0¿ Qué ?D1, y D2, así que_ZN4RectD0Evy _ZN4RectD2Evambas impresas como Rect::~Rect(): el destructor de eliminación y el destructor de objeto base comparten un nombre de fuente.

DemanglerEn elcode/main.pycubre ese subconjunto más los códigos de tipo de construcción de la sección 5.1.5.2 y el S_Las pruebas se realizan en los 19 nombres y se comparan con los capturados.llvm-cxxfilt --no-strip-underscoreLa bandera es importante en macOS, donde la herramienta elimina un subtítulo principal por defecto y luego no reconoce Z8describe...¿ Qué ?

El vtable de sus reubicaciones

La sección 2.5.2 de la ABI enumera los componentes de una tabla virtual en orden: offsets de llamada virtual, offsets de base virtual, offset a arriba, el puntero typeinfo y luego los punteros de función virtual. El punto de dirección, el valor que tiene vptr de un objeto, es la dirección del primer puntero de función (sección 2.6.2). Rectno tiene bases virtuales, por lo que su tabla se compensa a arriba, tipo de información y funciones.

El símbolo_ZTV4RectEn el objeto cada byte es cero. El contenido está en .rela.data.rel.ro._ZTV4Rect¿Qué es esto ?

output
0000000000000008 R_X86_64_64              _ZTI4Rect
0000000000000010 R_X86_64_64              _ZN4RectD2Ev
0000000000000018 R_X86_64_64              _ZN4RectD0Ev
0000000000000020 R_X86_64_64              _ZNK4Rect4areaEv
0000000000000028 R_X86_64_64              _ZNK4Rect4nameEv

La ranura 0 mantiene el desplazamiento hasta arriba, que es 0 y no necesita una reubicación.+16De ahí, la ranura 0 es el destructor de objetos completos, la ranura 1 es el destructor de borrado, la ranura 2area, apartado 3 name- ¿ Qué ?read_vtable()se lee el tamaño del símbolo, las reubicaciones en su rango y el cero en el desplazamiento 0. ShapeLa mesa tiene la misma forma que la de__cxa_pure_virtualen la ranura 2, que es la función de tiempo de ejecución que una llamada virtual pura alcanza.

RTTI

En la sección 2.9.4 se describe std::type_infocomo un vptr seguido de un indicador al nombre de tipo de descomposición, y enumera las clases derivadas. _ZTI4Recttiene tres traslados: _ZTVN10__cxxabiv120__si_class_type_infoE+0x10¿ Qué ?_ZTS4Rect, y _ZTI5ShapeEl primero es el vtable de__si_class_type_info, la clase que utiliza el ABI para un tipo con una única base pública no virtual en el punto de compensación cero, y el +0x10Es el punto de dirección de esa tabla.4Rect, que es el nombre del tipo de mangled, y name()en un type_infoLa tercera es la base._ZTI5Shapeusos __class_type_infoy no tiene puntero de base. read_typeinfo()devuelve estos tres hechos.

La llamada virtual

describeEn el-O0hace una llamada virtual:

output
       f: 48 8b 7d f8                  	movq	-0x8(%rbp), %rdi
      13: 48 8b 07                     	movq	(%rdi), %rax
      16: ff 50 10                     	callq	*0x10(%rax)

El primero .movqLa segunda lectura de 8 bytes en el desplazamiento 0 del objeto, que es el vptr, porque el ABI lo coloca primero en una clase con funciones virtuales.callqpasa por aquí .0x10pasado el VPT, y 0x10 / 8es la ranura 2.find_virtual_calls()coincide con el patrón y informa a ambos candidatos: Rect::area() constde la_ZTV4Recty __cxa_pure_virtualde la_ZTV5ShapeEl que se ejecuta depende del objeto, por lo que la llamada es indirecta.

DWARF registra la misma ranura.DW_TAG_subprogramlas entradas para areaen ambas clases de transporteDW_AT_vtable_elem_location (DW_OP_constu 0x2)¿ Qué ?namelleva la ranura 3, y los destructores llevan la ranura 0. Rectes un DW_TAG_structure_typede 16 bytes con una DW_TAG_inheritanceEntrada para Shapeen compensación 0 y miembros wa las 8 y hA las 12 .Shapees 8 bytes con un miembro artificial _vptr$ShapeDos derivadas independientes dan la ranura 2 para area: la tabla de reubicación y las entradas de desactivación.

En el-O1El objeto no tiene nada de vtable. mainconstrucciones Rectcomo local, así que el compilador resolvió r.name()y describe(r, 2)No se puede tomar la dirección de la tabla, por lo que la tabla no se emite.describetodavía contiene movq (%rdi), %raxy callq *0x10(%rax), porque su parámetro puede ser cualquierShape. Una tabla v que falta es un hecho sobre un archivo de objeto, y la prueba test_the_optimised_object_keeps_the_call_pattern_but_drops_the_vtables- ¿Qué? - Comprueba ambas mitades.

El ABI de Microsoft difiere en cada uno de estos detalles.?y codificar la convención de llamada, así que void test(void)Compilado como C++ __cdecl¿ Es verdad ??test@@ZAXXZEl localizador RTTI se encuentra en vfptr[-1]como un _RTTICompleteObjectLocator, y los símbolos son??_7para un vftable y ??_R4La documentación de Microsoft dice que "no hay estándares para nombrar en C++ entre los proveedores de compiladores".

Cómo las herramientas presentan los tipos recuperados

Cada fila que tiene un nombre de salida fue producida en esta máquina y capturada encode/fixtures/tools/Las filas para las herramientas que no están instaladas aquí describen su documentación y lo dicen.

ToolVersion, licenceWhere types liveOn our fixtures
Ghidra12.1.4, 2026-09-21, Apache-2.0Data Type Manager with built-in, user-defined, and derived types; .gdt archives; types used by a program are copied into itWith DWARF: /DWARF/account.c/account size=32 align=8 and a->balance = by + a->balance;. Without: *(int *)(param_1 + 4) with long param_1, tag(char *param_1), and *(char **)(param_1 + 0x18)
IDA Pro9.4, 2026-07-13, commercial; IDA Free is non-commercial with a cloud decompiler for x86 onlyLocal Types window, .til type libraries, DWARF and PDB import into a dwarf or pdb folderNot installed. Documentation only
Binary Ninja6.0, 2026-09-03, commercial with a Free editionType editing, header import and export, type archives, debug info from DWARF and PDB, type libraries, platform typesNot installed. Documentation only
radare26.2.4, 2026-10-05, LGPLv3ts, tsc, afvdts and tsc account printed nothing for the relocatable object with bin.dbginfo=true; pdf shows mov dword [rax + 4], ecx with source line comments from DWARF
rizin0.9.1, 2026-06-29, LGPL corets, tsc, afvdtsc account printed struct account { char kind; int balance; short code; double rate; const char *label; }; from DWARF; the build without -g lists only its bundled header types
lldblldb-1703.0.236.21image lookup -tbyte-size = 32 and the full compiler_type text from the ELF object
gdbnot installed hereptype /o prints "the offsets and sizes of fields in a struct" with holes markedDocumentation only
pyelftools0.33, public domainPython API over .debug_infoA 43-line script prints the same five members and unit version 5
llvm-dwarfdumpApple LLVM 17.0.0--debug-info, --debug-abbrev, --debug-str-offsetsThe oracle for every reader test

La salida de Ghidra sin DWARF muestra las mismas dos reglas en funcionamiento.char *al parámetro de tagporque la función compara un byte a través de ella, y char **al campo en 0x18Porque ...label_lengthDesreferencia el valor cargado y compara los bytes. Esas son las mismas dos reglas que el paso en esta lección aplica: ancho de la instrucción, puntero de la dereferencia. La documentación del decompiler de Ghidra dice que los varnodos de código p "no tienen necesariamente un tipo de datos asociado con ellos" y que el decompiler "al final asigna un tipo de datos formal" por propagación. La opción "Inferir indicadores constantes" extiende la misma propagación a las constantes. Ghidra 12.1 añadió la recuperación de bitfield al descompilador, que las fichas de esta lección no ejercen.

La tabla hecha a mano es la otra mitad de la historia. Ghidra's Data Type Manager, IDA's Local Types, Binary Ninja's type archives, y rizin's tdEl pase te dice qué escribir y qué campos estás adivinando.

rea 6.5.0 con su proveedor Ghidra devuelve el texto del descompilador como un registro. Todo lo que está en la fila Ghidra se aplica a él, y el registro lleva los nombres de tipos sin la evidencia que los produjo.python3 code/main.py inferen la misma lista le da esa evidencia.

Investigación de la interferencia de tipo

El paso aquí es la mitad de generación de restricciones de un sistema de inferencia de tipo, restringido a un estruto alcanzado desde un parámetro.

  • TIE (Lee, Avgerinos, Brumley, NDSS 2011) enmarcó la reconstrucción de tipo como generación de restricciones y resolución sobre una IR binaria, midió la conservaduría y precisión, y comparó con un descompilador comercial en 87 programas de núcleos.
  • Retypd (Noonan, Loginov, Cok, PLDI 2016) añadió subtipo, polimorfismo y tipos recursivos, y informó reconstrucción del puntero constUna implementación de código abierto existe bajo el nombre de GrammaTech.
  • OSPREY (Zhang y otros, IEEE S&P 2021) trata cada ubicación de memoria como una variable aleatoria e inferen los tipos más probables.
  • DIRTY (Chen y otros, USENIX Security 2022) y ReSym (Xie y otros, CCS 2024) producen descompiladores postproceso con modelos aprendidos, y ReSym informa con una precisión del 72,9% en diseños estructurales completos.
  • RecStruct (Chen y otros, USENIX Security 2026) recupera estructos anidados sin un descompilador, con ejecución simbólica insensible al flujo y unión-encuentra sobre ranuras de pila.

La estructura de la pila es lo primero que salta el pase de esta lección.-O1¿ Qué ?main¿ Qué es ?rowsen la pila y el código en línea dice 0x24(%rsp)¿ Qué ?0x40(%rsp)¿ Qué ?0x48(%rsp)¿ Qué ?0x30(%rsp), y 0x38(%rsp)Los modulos 32 son 4, 0, 8, 16 y 24: la misma estructura, sin parámetros para sembrar.

Construye el mismo

Ejecutar la demostración:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types
python3 code/main.py

La salida es un informe JSON con cinco partes. dwarfEl título de la unidad, las estructuras y las funciones de la-O0Objeto.oraclestiene el diseño de las reglas de psABI, el ctypesLa distribución, la distribución DWARF, la ejecución nativa y un agreebandera.buildscontiene los accesos, la disposición inferida y la comparación para cada uno de los tres objetos. cxxcontiene los nombres desfigurados, ambos vtables, ambos registros de tipoinfo, la llamada virtual y las ranuras DWARF. toolsenumera las salidas de la herramienta capturadas.

Forma esperada:

json
{
  "dwarf": {"unit": {"unitLength": 489, "version": 5, "unitType": "DW_UT_compile", "addressSize": 8, "abbrevOffset": 0}, "...": "..."},
  "oracles": {"agree": true, "psabiRules": {"size": 32, "align": 8, "...": "..."}, "...": "..."},
  "builds": {
    "O0": {"layout": {"stride": 32, "size": 32, "gaps": [1, 2, 3, 10, 11, 12, 13, 14, 15], "...": "..."}, "comparison": {"matches": 5, "members": 5, "...": "..."}},
    "O1": {"...": "..."},
    "O2": {"...": "..."}
  },
  "cxx": {"virtualCalls": [{"slot": 2, "candidates": {"Shape": "__cxa_pure_virtual", "Rect": "_ZNK4Rect4areaEv"}, "...": "..."}], "...": "..."}
}

Leer .code/main.pyen este orden:

  1. ElfObjectSe leían secciones, símbolos y reubicaciones, y relocated()se aplica S + Aen una sección de descomposición.
  2. parse_abbreviations()y DwarfInfo._parse_unit()Lea el encabezado, la tabla de abreviaturas y las entradas, luego resuelva strxy addrxa través de las dos mesas.
  3. DwarfInfo.struct_layouts()y functions()convertir las entradas en StructLayouty FunctionInforegistros, con type_name()¿ Qué ?type_size(), y type_kind()¿ Qué ?
  4. parse_listing()¿ Qué ?parse_memory(), y access_width()Leer el llvm-objdump¿Qué es eso?
  5. trace_function()es la máquina del estado abstracto, y fold_unrolled()y infer_layout()convertir los accesos en un diseño.
  6. compare_layouts()produce las filas de estado, y sysv_layout()y ctypes_layout()son los otros dos oráculos.
  7. Demangler¿ Qué ?read_vtable()¿ Qué ?read_typeinfo(), y find_virtual_calls()cubrir el objeto C++.

Hay cuatro órdenes más.python3 code/main.py dwarf FILE.oImprime las estructuras y funciones de cualquier objeto relocable de DWARF 5. python3 code/main.py infer LISTING.txt [FILE.o]ejecuta el pase en una lista y, con un objeto, compara con su DWARF. python3 code/main.py demangle NAME...y python3 code/main.py vtable FILE.ocubrir la mitad de C ++.

Usalo

En un nuevo objeto que construiste tú mismo:

  1. Captura .llvm-objdump -d -rEl pase lee ese texto.
  2. - ¿ Qué ?python3 code/main.py inferEn el listado. Leer primero la línea de testigos: nombra las funciones cuyo primer parámetro se utilizó como base de memoria. Si una función falta, su parámetro nunca alcanzó un operando de memoria, o llegó a uno a través de un camino que el paso no rastrea.
  3. Lea cada campo con su número de acceso. Un acceso es un testigo.
  4. Lea la evidencia de los pasos.call-siteo scaleLa entrada es fuerte.loop-stepLa entrada sin otro tipo es débil, porque un bucle desarrollado informa el paso de varios elementos.unrolledaparece, compruebe el pliegue a mano.
  5. Si un-gconstruir existe, ejecutar la comparación y leer cada fila que no es match- ¿ Qué ?no-evidencefila nombra un miembro no se ha tocado función.unexpectednombre de filas a un acceso que el pase no pudo colocar.
  6. Escriba el diseño en el administrador de tipos de su herramienta como un estructurado, y registre qué campos, nombres y tipos son suposiciones.

Mantenga los tres oráculos a su alcance.ctypes, y DWARF no está de acuerdo en un diseño que usted escribió, usted ha encontrado un estructurado, un campo de bits, o un objetivo con reglas diferentes.

Laboratorio interactivo

Laboratorio A: decodificar la entrada de miembro

Desde elcodedirectorio, Python abierto:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types/code
python3 -q
  • ¿Qué quieres decir ?
python
from main import *
elf = load_object("account-x86_64-O0-g.o")
dwarf = DwarfInfo(elf)
dwarf.header()
elf.section(".debug_info").data[0x1a3:0x1ac].hex(" ")
dwarf.dies[0x1a3].attributes
dwarf.type_name(dwarf.type_of(dwarf.dies[0x1a3]))

Decodifica los nueve bytes en papel con la abreviatura 13 antes de mirar el diccionario.elf.section(".debug_str_offsets").data[8:16].hex(), y el trasladado,elf.relocated(".debug_str_offsets")[8:16].hex(), y explicar la diferencia.

Laboratorio B: rastrear una función

python
listing = load_listing("account-x86_64-O0-g.objdump.txt")
trace = trace_function("label_length", listing["label_length"])
[(hex(a.address), a.root, a.offset, a.width, a.kind) for a in trace.accesses]
trace.pointer_uses
trace_function("sum_balances", listing["sum_balances"]).loops

Escriba, antes de ejecutar, que acceso prueba que el offset 24 es un puntero.-O1lista y encontrar el leaq 0x1(%rcx), %rcxEn eltrace.increments¿ Qué ?

Laboratorio C: doblar el bucle vectorial

python
o2 = load_listing("account-x86_64-O2-g.objdump.txt")
trace = trace_function("sum_balances", o2["sum_balances"])
sorted({a.offset for a in trace.accesses})
folded, evidence = fold_unrolled(trace, trace.accesses)
sorted({a.offset for a in folded}), [(e.kind, e.value, e.text) for e in evidence]

Entonces corre .analyze_build("O2", fold=False)["comparison"]y contar los unexpectedCambiar la regla de plegado para que requiere que el paso del bucle sea exactamentek * dconkal menos 2, y confirmen que el -O1el bucle, con un acceso y el paso 32, se deja solo.

Laboratorio D: el tablero y los nombres

python
shapes = load_object("shapes-x86_64-O0-g.o")
read_vtable(shapes, "_ZTV4Rect")
read_typeinfo(shapes, "_ZTI4Rect")
demangle("_ZNK4Rect4areaEv"), demangle("_Z8describeRK5Shapei")
find_virtual_calls(load_listing("shapes-x86_64-O0-g.objdump.txt"), [read_vtable(shapes, "_ZTV5Shape"), read_vtable(shapes, "_ZTV4Rect")])

Calcule la ranura desde callq *0x10(%rax)en papel, luego confirme que contraDwarfInfo(shapes).struct("Rect").virtual_methodsEntonces cargue .shapes-x86_64-O1-g.oy tratarread_vtableLeer el libro.ElfErrory explicar por qué el símbolo está ausente.

Laboratorio de práctica

Recupera la estructura de la pila en mainEn el-O1¿ Qué ?

Requisitos:

  • Extendersetrace_function()Así que una región de ranura de marco puede ser una raíz.movapsTiendas que llenan0x0(%rsp)¿ Qué ?0x5f(%rsp)de la.data.rel.ro, que es el inicializador de 96 bytes de rows¿ Qué ?
  • Atribuye los accesos en línea 0x24(%rsp)¿ Qué ?0x40(%rsp)¿ Qué ?0x48(%rsp)¿ Qué ?0x30(%rsp), y 0x38(%rsp)y reportar sus compensaciones modulo un candidato paso.
  • Derivar el paso 32 del bucle en 0x120¿ Qué ?0x12c, donde %r14comienza a las 4 y avanza a las 0x20hasta que0x64, y mostrar que el bucle se une0x64es tres elementos más el campo de compensación.
  • Comparar el resultado con el diseño de DWARF y alcanzar cinco coincidencias, y registrar qué tipos permanecen no probados.
  • Añadir una prueba que falla cuando la raíz del marco se atribuye a un parámetro.

El éxito significapython3 code/main.py inferen el -O1listas de listados maincomo un testigo con una raíz de marco, y la comparación muestra cinco coincidencias demain¿Por qué no?

Lección Artefacto

Esta lección incluye outputs/skill-recovering-data-and-types.md. Utilice para convertir una lista y una configuración opcional de defecto en un diseño estructural con evidencia por campo, un paso con sus fuentes, una comparación con DWARF y una lista de lo que permaneció desconocido. Requiere la tabla de acceso, la prueba de paso, la verificación de plegado, el oráculo DWARF cuando existe, y una declaración de nombres y calificadores que ninguna instrucción puede proporcionar.

Verifique el hecho

Ejecutar la demostración y la suite determinista:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types
python3 code/main.py
python3 -m unittest discover -s code/tests -v

La verificación deberá demostrar:

  • El título de la unidad, las 14 abreviaturas y los cinco elementos de compensación están de acuerdo con llvm-dwarfdump
  • Los nombres llegan a través de los relocalizados .debug_str_offsetsmesa y la unidad str_offsets_basey addr_baseson 8
  • Seis funciones llevan los rangos, DW_OP_reg6 RBP, y DW_OP_fbreglas ubicaciones que muestra el vertedero, y sus direcciones bajas son iguales a la función de la lista comienza
  • un objeto sin.debug_infoeleva, y el decodificador LEB128 maneja 0x78como -8
  • pyelftoolsy lldbimprime el mismo diseño que el lector
  • las reglas de psABI, ctypes, DWARF, y la carrera nativa dan compensaciones 0, 4, 8, 16, 24y tamaño 32, y el estruct reordenado es de 24 bytes
  • el parser de la lista lee 13 instrucciones en bump, adjuntos .R_X86_64_PLT32 bump-0x4a la llamada en 0x194, y encuentra el salto hacia atrás en 0x72
  • operandos de memoria, anchos, tipos y signos decodificados del texto AT&T y el mnemónico
  • bumpEn el-O0lee su puntero hacia atrás desde la ranura del marco y hace tres accesos en el despacho 4
  • El paso viene de shlq $0x5En el-O0, desde addq $0x20, %rdxEn el-O1, y de ocho cargas dobladas por un paso de 256 en -O2
  • label_lengthprueba el puntero en el desplazamiento 24 desreferenciándolo
  • mainpasa un objeto de marco en compensaciones 0, 0x20, y 0x40En el-O0y no llama nada más queprintfEn el-O1
  • Cada construcción coincide con cinco de los cinco miembros con tamaño 32 y huecos iguales al relleno DWARF
  • sin doblar el -O2crear informes siete campos más allá de byte 32 y no tamaño
  • El desangler reproduce todos los 19 llvm-cxxfiltcuerdas
  • tanto las tablas de vtables, tanto los registros de tipoinfo, y la llamada virtual en 0x16Resolver a la ranura 2, y DWARF está de acuerdo
  • el -O1Objeto C++ mantiene el patrón de llamada y no tiene vtable
  • Las capturas de Ghidra, rizin y radar2 muestran la estructura con DWARF y los desplazamientos crudos sin ella
  • La figura interactiva incorpora los mismos datos que el análisis produce

Modos de falla de producción

FailureWhat the weak tool reportsWhat the analysis must prove
Unrolled loopEight fields 32 bytes apart and a 232-byte structLoop step 256 equals 8 times the offset delta 32, so one field at offset 4
Stride read from one incrementsizeof 256 from addq $0x100The greatest common divisor of every stride source, with the scalar tail's addq $0x20
8-byte field called a pointervoid * at offset 24A later access uses the loaded value as a base, or the kind stays int64
Pointer called an integerlong at offset 24The dereference at 0xfb, recorded as pointer proof
Narrow comparechar with a signcmpb carries no sign; only movsbl or movzbl does
Five functions assumed to share a typeOne struct with no evidence of aliasingCall sites in main that pass one frame object, or an explicit analyst assertion
Strings read from .debug_str by indexGarbage names from a relocatable objectstr_offsets_base plus the relocated .debug_str_offsets entry
Addresses read as numberslow_pc of 0 for every functionaddrx through the relocated .debug_addr table
DWARF 4 objectA reader that misparses the headerVersion check on the second header field; 4 has no unit_type byte
Missing vtable"the class has no virtual functions"The -O1 object inlined every call; the -O0 object has the table and its relocations
Vtable slot read from the wrong baseOff-by-two slot numbersThe address point is 16 bytes past the symbol, after offset-to-top and typeinfo
Mangled name strippedZ8describeRK5Shapei left as is--no-strip-underscore on macOS, or a demangler that checks for _Z
Struct typed from one tool's guesschar *param_1 taken as the field typeThe width and the dereference, written as evidence, and the name marked as a guess

Regla de funcionamiento

Recupere un campo de un acceso y un paso de un bucle, y adjunta la instrucción a cada uno. Doble los bucles desarrollados antes de confiar en un desplazamiento que pase el paso. Lea DWARF a través de sus abreviaturas y sus tablas reubicadas, nunca indexando en una sección de cadena. Comparar campo por campo y escribir cada miembro sin evidencia, cada puntero no probado, y cada nombre como desconocido. En un objeto de C ++, lea la tabla de sus reubicaciones y la ranura de la llamada, luego use DWARF para confirmar la ranura que ha leído.

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.