Idiomas de compilador: qué hace la optimización de su código y cómo leerlo de nuevo
Type: Build
Languages: Python, C, x86-64 assembly, AArch64 assembly
Prerequisites: Phase 13 · 33 (disassembly and control flow), Phase 13 · 34 (lifting to pseudocode), Phase 13 · 38 (calling conventions)
Time: ~150 minutes
Objetivos de aprendizaje
- Derivar el número mágico y cambiar para la división por cualquier constante con el Granlund-Montgomery ligado, y recuperar el divisor de las constantes emitido clang.
- Nombre de las cinco formas en que LLVM reduce un
switchy leer el recuento de entradas, la ubicación de la tabla y las entradas del objeto. - Lea un bucle vectorizado como carriles, acumuladores, elementos por iteración, máscara de recuento triples, reducción y bucle restante.
- Reconocer llamadas de cola, callees en línea, selecciones sin ramas, y los bucles que se convirtieron en
memsetomemcpy¿ Qué ? - Informar cada idioma con sus instrucciones de prueba y su formulario fuente decodificado, en x86-64 y en AArch64, en
-O0por el-O3¿ Qué ? - Comparar el resultado con lo que Ghidra, Angr, radar2, rizin, y lldb muestran para los mismos bytes.
El problema
Usted compiladoidioms.cEn el-O2y preguntó a un agente qué.div7uEl agente leyó.imulq $0x24924925, %rax, %raxy respondió que la función se multiplica por 613566757, cambia y se suma.x / 7¿ Qué ?
El mismo objeto tiene unswitchCon siete casos que no tienen salto en absoluto, un switchcon cinco casos que es una mesa de salto, un switchcon dos casos que es un setey un cmovne, y un bucle hacia arriba nnúmeros enteros que ejecuta ocho números enteros por iteración en dos xmmEl sistema de registro de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de datos de cuyo de cuyo sitio de cuyo sitio de cuyo sitio de cuyo sitio de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuenta de cuyo sitio de cuenta de cuenta de cuyo sitio de cuenta de cuenta de cuyo sitio de-O0la misma fuente producida divl, siete entradas de salto de mesas, y un bucle escalar.-O1el interruptor de siete casos se convirtió en una tabla de valores y el bucle vectorial aún no existía.
Estas formas son idiomas. Un compilador emite cada una de ellas de una regla que se puede leer en su fuente, y cada una puede ser decodificada de vuelta a la construcción de la que proviene. La decodificación es aritmética, y la aritmética se puede comprobar.0x66666667Esta lección construye el reconocedor para todos ellos, lo ejecuta en 18 versiones de nuestro propio código, y verifica todos los 373 informes contra la fuente C.
Un idioma es una forma con una derivación
Un idioma es una secuencia de instrucciones que el compilador emite para una construcción de fuente. Tres cosas hacen que sea reconocible. Tiene una forma fija, los operandos llevan constantes que la derivación predice, y la forma difiere de lo que un programador escribiría a mano.
El reconocedor en code/main.pytrabajos sobre el texto de llvm-objdump -d -r, que la lección 33 estableció como el oráculo para los límites de instrucciones. analiza cada función en instrucciones con dirección, bytes, mnemónica, operandos y la línea de reubicación que la sigue..rodata, donde se encuentran las tablas de valores, y el .rela.rodataentradas, donde los objetivos de la mesa de salto viven antes de vincularse.
Cada detección es unIdiomregistro con un kind, un decodificado .sourceformulario, una lista de evidenceDirecciones, a detailDiccionario y unverifiedLa bandera proviene de la fuente C.parse_source()Leíaidioms.c, encuentra cada función, y extrae los divisores después /y %, los multiplicadores después de *, el switchLos casos y sus valores, la? :Los operadores, losforUn idioma de división se verifica cuando su divisor está en la lista de división de la función. Una tabla de búsqueda se verifica cuando sus entradas coinciden con los valores del caso, hasta un factor constante. Una llamada de cola se verifica cuando se llama a la llamada en la fuente. El informe demo termina con un unverifiedlista, y la suite de pruebas afirma que la lista está vacía para todas las 18 construcciones.
El reconocedor ejecuta un pequeño evaluador simbólico sobre los bloques básicos de cada función, en orden de lista, con los nombres de parámetros de la fuente en los registros de argumentos de las convenciones del Sistema V y AAPCS64 de la lección 38.-O0En cada entrada de bloque los estados de los predecesores se fusionan, y donde difieren un nombre que un idioma anterior asignado gana sobre una constante.cmovll %edx, %eaxEn eldigestSe lee como sum % 10 < 5 ? sum % 10 : 5: el lenguaje de bucle llamado acumulador sum, el lenguaje de división nombró su cuotiente sum / 10, y el modulo idiom nombró su resultado sum % 10La fuente dice:rem > 5 ? 5 : remAmbas expresiones dan el menor de remy 5, y están de acuerdo en rem == 5¿ Qué ?
División por una constante
Granlund y Montgomery, "División por integros invariantes mediante multiplicación" (PLDI 1994), sustituyen n / dcon un multiplicador y un turno.sy un multiplicador m = ceil(2^s / d)Entonces ...floor(n * m / 2^s)igual floor(n / d)por cada uno nen el rango cuando el error e = m * d - 2^sEs bastante pequeño. Para sin firmar de 32 bits.nLa línea es e <= 2^(s - 32). Para firmarncon|n| < 2^31La línea es e <= 2^(s - 31), y una fijación de señal añade 1 cuando nes negativo, porque el piso rodea en el sentido equivocado para los cuotientes negativos.
magic_numbers(d, bits, signed)En elmain.pybúsquedas sLa búsqueda reproduce cada constante de sonido emitido en los accesorios.
10 sin firmar a mano
2^35 / 10 = 3435973836.8El techo es3435973837 = 0xCCCCCCCDEl error es0xCCCCCCCD * 10 - 2^35 = 2, y el límite en s = 35¿ Es verdad ?2^3 = 8- En els = 34El error sería 6 contra un límite de 4, por lo que 35 es el cambio más pequeño.n = 123¿ Qué es esto ?123 * 3435973837 = 422624781951, y 422624781951 >> 35 = 12¿ Qué ?
Clang emite exactamente esto paradiv10uEn el-O2en el punto x86-64:
text 0: 89 f9 movl %edi, %ecx
2: b8 cd cc cc cc movl $0xcccccccd, %eax
7: 48 0f af c1 imulq %rcx, %rax
b: 48 c1 e8 23 shrq $0x23, %rax0x23Es 35.movl %edi, %ecxEl reconocedor lee el multiplicador de la letra movl $immque alimenta a los dos operandos.imulq, lee el turno y llamadivisor_from_magic(0xCCCCCCCD, 35)Esa función intenta2^35 // m = 10y 11, y acepta 10 porque magic_numbers(10)da el mismo multiplicador y se desplaza hacia atrás.
En AArch64 la misma función es mov w8, #0xcccd; movk w8, #0xcccc, lsl #16; umull x8, w0, w8; lsr x0, x8, #35La constante llega en dos mitades de 16 bits, el multiplicador es umullEl reconocedor reúne las mitades y informa el mismo divisor.
7 sin firmar a mano, con la corrección adicional
2^35 / 7 = 4908534052.57El techo es4908534053 = 0x124924925, que necesita 33 bits. El error es 3 contra un límite de 8, por lo que el cambio es 35, y ningún cambio menor funciona. Un multiplicador de 32 bits no puede sostener el multiplicador, por lo que el compilador se multiplica por los 32 bits bajos0x24924925y suma el dividendo de nuevo en una secuencia fija:
text 32: 48 69 c0 25 49 92 24 imulq $0x24924925, %rax, %rax
39: 48 c1 e8 20 shrq $0x20, %rax
3d: 29 c7 subl %eax, %edi
3f: d1 ef shrl %edi
41: 01 f8 addl %edi, %eax
43: c1 e8 02 shrl $0x2, %eaxLeerlo como t = (n * 0x24924925) >> 32, entoncesq = (((n - t) >> 1) + t) >> 2El cambio total es32 + 1 + 2 = 35- Conn = 100¿ Qué es esto ?t = 14¿ Qué ?(100 - 14) >> 1 = 43¿ Qué ?43 + 14 = 57¿ Qué ?57 >> 2 = 14El reconocedor detecta elsub¿ Qué ?shr¿ Qué ?add¿ Qué ?shrcola después de un cambio de 32 bits, agrega 2^32regresar al multiplicador, y se recupera 7. evaluate_division()corre esta secuencia exacta y el test lo verifica contran // 7En el formulario AArch64 se encuentra sub w9, w0, w8; add w8, w8, w9, lsr #1; lsr w0, w8, #2, una instrucción más corta porque la suma desplazada es una instrucción.
Firmado 3 a mano
2^32 / 3 = 1431655765.33, techo1431655766 = 0x55555556, error 2, y el límite firmado en s = 32¿ Es verdad ?2^1 = 2El límite se mantiene sin espacio libre, así que el turno es 32 y el clang utilizashrq $0x20- Conn = -7: el producto es -10021590362, el cambio aritmético por 32 da -3, el bit de signo está establecido, por lo que la fijación agrega 1 y el resultado es -2, que es -7 / 3en C. Los accesorios firmados div10sy div3sLos dos llevan el arreglo comoshrq $0x3fen una copia del producto seguida de addl¿ Qué ?
Lo que las constantes no son
El multiplicador solo no nombra divisor.0x66666667El multiplicador con el cambio 33 es 5, como se mostró en la lección 34.clangMatchesAlgorithmLos registros de campo que el par es el que la búsqueda produce.magic_numbers()los rechaza, porque el compilador emite un cambio con un sesgo de signo en su lugar, y mod8sen la siguiente sección muestra esa forma.
El idioma pertenece al selector de instrucciones
En el-O0¿ Qué ?div10u¿ Es verdad ?movl $0xa, %ecx; xorl %edx, %edx; divl %ecx- ¿ Qué ?div10s¿ Es verdad ?cltd; idivl %ecxEn el mismo objeto,digestdivide su suma de bucle por 10 con imulq $0x66666667Una bandera explica la diferencia.idioms-x86_64-nofastisel-O0.oFue construido con -O0 -mllvm -fast-isel=false, y en ella cada división es el multiplicador, incluyendo div10uconmovl $0xcccccccd, %ecx; imulq %rcx, %rax; shrq $0x23La división de hardware es la elección del selector de instrucciones rápidas en -O0. El multiplicador es la disminución de SelectionDAG, que es lo que LLVM UnsignedDivisionByConstantInfoy SignedDivisionByConstantInfoLa producción de productos.digestEl bloque que sostiene la división también sostiene unswitchEl reconocedor informa de ambos formularios,hardware idivly magic multiply, y la figura muestra la mezcla-O0La función.
Modulo sigue el cuotiente
x % d¿ Es verdad ?x - (x / d) * dEl compilador calcula el cuotiente con el idioma anterior, lo multiplica y restrae.addl %ecx, %ecx; leal (%rcx,%rcx,4), %ecx, que es 2 * q * 5En AArch64 es una instrucción.msub w0, w8, w9, w0, que calcula .w0 - w8 * w9conw9 = 10El reconocedor sigue el registro de los cuotientes a través de copias, sigue el factor acumulado a través de add¿ Qué ?lea¿ Qué ?shl, y imul, y acepta unasubcuando el factor es igual al divisor.
Cuando el mismo coeficiente también alimenta otra expresión, ambos idiomas permanecen en el informe.-O1el compilador computa sum / 10Una vez en .digesty lo utiliza para el switchEn x86-64 incluso combina el cuotiente dos veces,leal (%rax,%rsi), %edipara el modulo y addl %esi, %eaxEl reconocedor informa una división y un módulo y marca el cuotiente como compartido.switchel índice no se puede leer.
El modulo firmado por una potencia de dos tiene su propia forma, porque el cuotiente nunca se calcula:
text 92: 8d 48 07 leal 0x7(%rax), %ecx
95: 85 ff testl %edi, %edi
97: 0f 49 cf cmovnsl %edi, %ecx
9a: 83 e1 f8 andl $-0x8, %ecx
9d: 29 c8 subl %ecx, %eaxPara un negativo xEl código se desvía por 7 antes de la mascarada, de modo que la truncation gira hacia cero como C requiere.xredondeado a un múltiplo de 8, y la restancia deja x % 8. AArch64 lo hace con negs w8, w0; and w9, w0, #0x7; and w8, w8, #0x7; csneg w0, w9, w8, mi: mascarar a ambos xy -xGhidra llama su regla para esto.RuleSignMod2nOpt, con el resumen "Convertir los formularios INT_SREM: (V + (sign >> (64-n)) & (2^n-1)) - (sign >> (64-n)) => V s% 2^n" . " .
En el-O0AArch64 escribe modulo como sdiv w9, w8, w10; mul w9, w9, w10; subs w0, w8, w9El reconocedor también acepta ese formulario y informa.hardware sdiv + mul + subs¿ Qué ?
Cinco maneras de bajar el interruptor
La LLVM decide la forma de unswitchEn dos lugares.SimplifyCFG::simplifySwitchLookupse ejecuta en-O1y arriba y convierte un interruptor cuyos casos producen constantes en una tabla de valores, cuando hay al menos tres casos y el objetivo puede construir tablas de búsqueda.SingleValueKind¿ Qué ?LinearMapKind¿ Qué ?BitMapKind, y ArrayKind- El .-O0El oleoducto nunca pasa por este paso.SwitchLowering::findJumpTablesen la selección de instrucciones, que construye una mesa de salto cuando el grupo tiene al menos min-jump-table-entriesentradas, 4 por defecto, y es lo suficientemente denso: NumCases * 100 >= Range * MinDensityconjump-table-density10 para funciones normales y 40 para funciones inferiores a -OsLo que queda se convierte en una búsqueda binaria sobre las constantes de caso, con splitWorkItemLas instalaciones tienen una función por resultado.
| Function | Source | -O0 x86-64 | -O2 x86-64 | -O2 AArch64 |
|---|---|---|---|---|
linear_switch | cases 0 to 6 return 10 to 70 | jump table, 7 entries | linear map 10*k + 10, default -1 | madd and csinv |
table_switch | cases 0 to 6 return 10 to 60 then 71 | jump table, 7 entries | lookup table at .rodata+0x28 | lookup table at .rodata+0x0 |
call_switch | cases 0 to 4 call one of five pointers | jump table, 5 entries | jump table, 5 entries | comparison tree, pivot 1 |
sparse_switch | cases 1, 100, 10000, 1000000, 100000000 | compare chain | comparison tree, pivot 9999 | comparison tree, pivot 9999 |
two_cases | cases 3 and 4 return 7 and 9 | compare chain | select chain k != 3 ? (k == 4)*9 : 7 | two csel |
La tabla de búsqueda, leída desde el objeto
table_switchEn el-O2¿Qué es esto ?
text d0: b8 ff ff ff ff movl $0xffffffff, %eax
d5: 83 ff 06 cmpl $0x6, %edi
d8: 77 0c ja 0xe6 <table_switch+0x16>
da: 89 f8 movl %edi, %eax
dc: 48 8d 0d 00 00 00 00 leaq (%rip), %rcx
00000000000000df: R_X86_64_PC32 .rodata+0x24
e3: 8b 04 81 movl (%rcx,%rax,4), %eax
e6: c3 retqLos límites de controlcmpl $6; jaEl aditivo de reubicación 0x24más los 4 bytes del campo relativo al RIP pone la tabla en .rodata+0x28, la lección de la regla 33 derivado.0a 00 00 00 14 00 00 00 ..., así que las entradas son [10, 20, 30, 40, 50, 60, 71], y son iguales a los valores de la fuente de caso..Lswitch.table.table_switchen el .sarchivo, con un comentario @switch.table.table_switchEn el objeto sólo quedan la reubicación y los bytes. No hay salto, por lo que el gráfico de flujo de control tiene tres bloques.
El formulario AArch64 pone la dirección de la tabla junto con adrp x8y add x8, x8, #0x0, cada uno llevando su propia reubicación a .rodata, y cargas con ldr w0, [x8, w0, uxtw #2]El reconocedor lee el adjunto de laaddel traslado.
El mapa lineal, sin una tabla en absoluto
linear_switchretorno 10 * (k + 1)porken 0 a 6. simplifySwitchLookupdetecta la relación afina y emite aritmética:
text b0: 83 ff 07 cmpl $0x7, %edi
b3: 8d 04 bf leal (%rdi,%rdi,4), %eax
b6: 8d 4c 00 0a leal 0xa(%rax,%rax), %ecx
ba: b8 ff ff ff ff movl $0xffffffff, %eax
bf: 0f 42 c1 cmovbl %ecx, %eaxEl evaluador lee leal (%rdi,%rdi,4)¿ Cómo ?5*k¿ Qué ?leal 0xa(%rax,%rax)¿ Cómo ?10*k + 10, y el cmovbdespués decmpl $7¿ Cómo ?k < 7 ? 10*k + 10 : -1El reconocedor verifica la fórmula con cada caso de origen y marca el idioma verificado.madd w8, w0, w8, w9y csinv w0, w8, wzr, lo, donde csinvconwzrrendimientos -1en el camino predeterminado.
La mesa de saltos, leída de las mudanzas
call_switchEn el-O2¿Es la lección de forma 33 recuperada en gradeEn el-O0¿ Qué es esto ?cmpl $4; ja, una leaq¿ Qué ?.rodata-0x4, una movslqde una entrada de 4 bytes, addq, y jmpq *%raxLas cinco entradas son reubicaciones en.rela.rodata, y target = addend - 4kda .0x10c, 0x107, 0x10e, 0x110, 0x109, cinco bloques distintos que cada cola salta a través de un puntero de función.dispatchfixture escala esto a 16 casos: 16 entradas de reubicación en x86-64, y en AArch64 una tabla comprimida de 16 bytes únicos leídos con ldrb w11, [x9, x8], escalado por 4 con add x10, x10, x11, lsl #2, y basado en unadrEl reconocedor decodifica[0x24, 0x74, 0x54, ...]de la tabla de byte y los informes compressed: true¿ Qué ?
El mismo interruptor en cuatro niveles
digesttiene un interruptor encendido sum / 10con casos 6 a 10 y valores 1, 2, 4, 8, 8, entonces multiplica el resultado por 16. en x86-64 es una tabla de salto en -O0, una mesa de búsqueda en -O1con entradas [16, 32, 64, 128, 128], y una mesa de salto de nuevo en -O2y -O3cuyos cuatro bloques de caja se almacenan 0x10¿ Qué ?0x20¿ Qué ?0x40¿ Qué ?0x80- Desde-O1En el caso de los casos, la multiplicación por 16 ha aumentado.-O0Los casos almacenan 1, 2, 4 y 8, y la multiplicación permanece después del cambio.-O1tabla de búsqueda el reconocedor compara las entradas con los valores de origen, encuentra una relación constante, y informa foldedFactor: 16En el AArch64 el-O2forma es un árbol de comparación con un pivote en 8, pruebas de igualdad para 6, 7 y 8, y una prueba de rango sub w9, w9, #9; cmp w9, #2; b.hsEl reconocedor informa que los casos 9 y 10 se cubren en dos instrucciones.rangeTests: [[9, 10]]¿ Qué ?
Dos banderas mueven estos límites.-fno-jump-tables- ¿ Qué ?table_switch¿ Qué ?call_switch, y el digesten el arco de comparación en x86-64, con los pivotes 2 y 4 para la función de siete casos.-mllvm -min-jump-table-entries=4Las hojascall_switchEn el caso de los grupos de referencia, el número de entradas no era el factor decisivo para un cambio de cinco casos de llamadas indirectas.idioms-arm64-jt4-O2.oregistros que funcionan.
El CCG aplica ambas decisiones .tree-switch-conversion.cc, con las clases jump_table_cluster¿ Qué ?bit_test_cluster¿ Qué ?switch_decision_tree, y switch_conversion- Es un .-ftree-switch-conversionestá en -O2¿ Qué ?-fjump-tablesy -fbit-testsEn el-O1, y el umbral de la mesa de salto proviene decase-values-thresholdNo hay GCC instalado en esta máquina, por lo que esos nombres provienen del código fuente y manual del GCC, y el Compiller Explorer es donde los comprueba.
Los bucles: Vectorizados, Desarrollados o reemplazados
El vectorizador de bucle de LLVM está encendido en -O2¿ Qué ?-O3, y -Os, y fuera en -O0y -O1, una regla que data de LLVM 3.4 en enero de 2014. sum_nEn el-O2muestra cada parte de la forma:
text 1f6: 83 fe 07 cmpl $0x7, %esi
1f9: 77 09 ja 0x204 <sum_n+0x14>
206: 81 e2 f8 ff ff 7f andl $0x7ffffff8, %edx
230: f3 0f 6f 14 37 movdqu (%rdi,%rsi), %xmm2
235: 66 0f fe c2 paddd %xmm2, %xmm0
239: f3 0f 6f 54 37 10 movdqu 0x10(%rdi,%rsi), %xmm2
23f: 66 0f fe ca paddd %xmm2, %xmm1
243: 48 83 c6 20 addq $0x20, %rsi
24a: 75 e4 jne 0x230 <sum_n+0x40>
24c: 66 0f fe c8 paddd %xmm0, %xmm1
250: 66 0f 70 c1 ee pshufd $0xee, %xmm1, %xmm0
262: 66 0f 7e c8 movd %xmm1, %eax
270: 03 04 97 addl (%rdi,%rdx,4), %eax
279: 75 f5 jne 0x270 <sum_n+0x80>El guardia .cmpl $7; jaEnvía el recuento de viaje por debajo de 8 al bucle escalar.andl $0x7ffffff8Las rondas del viaje cuentan hasta un múltiplo de 8. El cuerpo carga 32 bytes por iteración en dos xmmacumuladores, cuatro carriles de 32 bits cada, por lo que el factor de vectorización es 4 y el factor de interleave es 2. Después del bucle, paddd, dos .pshufd, dos más .paddd, y movdReducir los ocho carriles a un número entero.0x270El reconocedor encuentra el bucle desde su borde trasero, ve los registros SIMD, lee el recuento de carril desde paddd, cuenta acumuladores distintos, divide el paso 0x20por el 4 y por los informes 4 lanes x 2 accumulators = 8 elements per iteration, con la máscara, el guard, la reducción y el ciclo restante como campos.ldp q2, q3, dos .add v.4s¿ Qué ?addv s0, v0.4s, y fmov w8, s0¿ Qué ?
Un recuento fijo de viajes elimina el bucle. sum4Es uno .movdqu, la reducción y movd, sin borde trasero.poly8no puede vectorizar, porque cada iteración depende del acumulador anterior, por lo que el compilador lo despliegue ocho veces: ocho cargas en compensaciones de 0 a 0x1cde la%rdi, cada uno seguido de un cambio por 5 y una restancia que calcula acc * 31El reconocedor cuenta las cargas en compensaciones consecutivas desde una base y informa loop unrolled by 8, no back edgeEn el AArch64 las mismas cargas llegan como cuatro .ldp- En parejas.
Apague el vectorizador con -fno-vectorize -fno-slp-vectorizey sum_nse convierte en un bucle escalar desarrollado por 8, con ocho addl k(%rdi,%rdx,4), %eaxy addq $8, más un bucle restante.sum4El reconocedor informaunrolled loopconcopies: 8y indexStep: 8para la primera y copies: 4Por el segundo.
Lección 33 encontró dos bordes traseros en totalEn el-O2En esta lección se les da el nombre de un cuerpo vectorial y un resto escalar del mismo bucle fuente.is_loop()¿Qué es eso?digesten AArch64 contiene ramas retrocedientes a un bloque predeterminado compartido que no son bucles, y la primera versión del reconocedor las contó como bucles vectoriales.
Los bucles que se convierten en llamadas de la biblioteca
LoopIdiomRecognizetransforma un bucle que almacena cero en cada byte en una llamada a memset, desde -O1hacia arriba.fillEn el-O2¿ Es verdad ?testl %esi, %esi; jle; movl %esi, %edx; xorl %esi, %esi; jmp memset@PLTLa llamada es un salto de cola con la reubicaciónR_X86_64_PLT32 memset-0x4, y la función es retqEl reconocedor informa que el sistema de detección de los virus está en el camino vacío.store loop recognized as memset(...)y lo verifica contra el bucle fuente, porque la fuente nunca nombra memset¿ Qué ?
copy16llamadasmemcpycon una constante 16, y el backend x86 lo expande en línea como movups (%rsi), %xmm0; movups %xmm0, (%rdi), dentro de su límite de 8 tiendas pormemcpy- En el-O0La misma llamada es dos 8 bytes movq- En parejas.-fno-builtinLa llamada sigue siendo una llamada,jmp memcpy@PLTconmovl $0x10, %edx, y fillmantiene su bucle, que el vectorizador luego convierte en un bucle de dos 16 bytes movupsLos registros de reconocimiento de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de la red de datos de datos de la red de datos de datos de datos de la red de datos de datos de datos de la red de datos de datos de datos de datos de la red de datos de datos de datos de datos de la red de datos de datos de datos de datos de la red de datos de datos de datos de datos de la red de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de datos de la red de datos de datos de datos de datos de la red de datos de datos de datos de datos de datos de la red de datos de de de de datos de datos de de de de datos de de de de de de de de de de de de de de de de de de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de un de de de un de un de un de de un de un de un de de de un de unvectorized store loop: 2 x 16-byte stores = 32 bytes per iterationpara esa construcción.
Llamadas que desaparecen o se convierten en saltos
tail_callerretorno tail_target(x + 1)- En el-O0Es verdad.addl $0x1, %edi; callq tail_target@PLT; addq $0x10, %rsp; popq %rbp; retq- En el-O2Es verdad.incl %edi; jmp tail_target@PLTEl marco del llamador se ha ido y el llamador vuelve al llamador.b tail_targetconR_AARCH64_JUMP26El reconocedor trata a unjmpo bLa descesión recursiva de la lección 33 debe tratarla como una salida de función, porque el objetivo está fuera de la función.
twice_sumllamadasleaf_add- En el-O0La llamada está ahí,callq leaf_add@PLT- Desde-O1el cuerpo de leaf_add¿ Qué ?leal (%rdi,%rsi), %eax, aparece dentro .twice_sumY no queda ninguna llamada.-O0Clang marca cada función optnoney noinlineEl reconocedor sabe por la fuente quetwice_sumllamadasleaf_add, no encuentra ninguna llamada o salto de cola a él, y luego busca la forma del cuerpo de la llamada en el llamador: la mnemónica y la fuente operands con el destino eliminado, por lo que add w8, w1, w0en los pares de llamadas de AArch64 add w0, w1, w0Una coincidencia es la evidencia.
dispatchcombina ambas cosas: una mesa de salto de 16 entradas cuyos casos sonmovl %esi, %edi; jmp fN@PLTEl reconocedor informa de un interruptor y 16 llamadas de cola, todas verificadas en comparación con las 16 llamadas en la fuente.
Las ramas que se convirtieron en datos se mueven
clamp_max¿ Es verdad ?movl %esi, %eax; cmpl %esi, %edi; cmovgl %edi, %eaxLos nombres de los evaluadores%edi¿ Cómo ?ay %esi¿ Cómo ?b, registra la comparación y lee lacmovg¿ Cómo ?a > b ? a : b, que es el texto fuente. abs_int¿ Es verdad ?movl %edi, %eax; negl %eax; cmovsl %edi, %eaxy se lee como -x < 0 ? x : -x; AArch64 lo escribe como cmp w0, #0; cneg w0, w0, miy se lee como x < 0 ? -x : x- ¿ Qué ?is_pos¿ Es verdad ?xorl %eax, %eax; testl %edi, %edi; setg %al, que se lee como (x > 0)- Estos son ...-fif-conversionEn palabras del CCG y cmovLa selección en los LLVM.X86CmovConversionEl paso puede dar una vuelta.cmovde nuevo en ramas cuando juzga que rentable, por lo que la misma fuente puede mostrar cualquiera de las formas en un bucle.
En el-O0Ninguno de ellos existe.clamp_maxes una comparación, una jleEl reconocedor no informa nada por ello en el-O0y branchless selectde la-O1, que es uno de los hechos que la figura hace visibles.
mul9Es uno .leal (%rdi,%rdi,8), %eax, y los informes del reconocedor strength reductionconx * 9, porque la fuente se multiplica por 9. Sólo lo hace cuando la fuente tiene el factor; a leaque escalas un índice de tabla no es una multiplicación.
Dos características que hacen que el corazón se endurezca
El protector de pila es una característica de compilador, y deja una forma que un agente leerá de otra manera como lógica del programa. canary.cCompilado con -fstack-protector-strongpara x86-64 Linux da:
text 7: 64 48 8b 04 25 28 00 00 00 movq %fs:0x28, %rax
10: 48 89 44 24 40 movq %rax, 0x40(%rsp)
26: 64 48 8b 0c 25 28 00 00 00 movq %fs:0x28, %rcx
2f: 48 3b 4c 24 40 cmpq 0x40(%rsp), %rcx
34: 75 05 jne 0x3b <with_canary+0x3b>
3b: e8 00 00 00 00 callq 0x40 <plain_leaf>
000000000000003c: R_X86_64_PLT32 __stack_chk_fail-0x4%fs:0x28¿ Es verdad ?stack_guarden Glibc's tcbhead_t, en byte offset 40, y LLVM getIRStackGuardEl prólogo copia la guardia sobre el amortiguador, el epílogo compara y el camino de fracaso llama__stack_chk_failEn AArch64 Linux el guardián proviene del global .__stack_chk_guarda través de la GOT con adrpy ldr, y el fracaso esbl __stack_chk_failEl reconocedor informa la fuente de la guardia, la ranura, la comparación y la llamada de fallo.plain_leaf, que no tiene array, no tiene canario bajo strong, que protege las funciones con matrices,allocaLas notas de investigación también registran el formulario MSVC en el apartado/GS, una galleta mezclada con %rspy verificado por __security_check_cookie; no existe en esta máquina un enlace MSVC, por lo que la forma se describe únicamente a partir de la salida del montaje del compilador.
-fcf-protection=fullPone en elendbr64, bytes f3 0f 1e faEs una plataforma de aterrizaje para ramas indirectas en procesadores con control de flujo de ejecución, y se ejecuta como unnopLos patrones de inicio de la función x86-64 de Ghidra incluyen elendbr64Variantes prefijadas de push rbp; mov rbp, rspEl reconocedor lo informa como:cet landing pad, así que una función que comienza con cuatro bytes inusuales no se confunde con datos.
Lo que los decomponedores doblan
Cada decompiler en la lección 37 ejecuta algunas de estas derivaciones como reglas de reescritura.ruleaction.cc- ¿ Qué ?RuleDivOptes "Convertir INT_MULT y cambiar los formularios a INT_DIV o INT_SDIV", con los formularios sub( (zext(V)*c), d) >> e => V / (2^n/(c-1)) where n = d*8 + ey la variante firmada con sexty s>>- ¿ Qué ?RuleModOptes "Simplificar expresiones que optimizan INT_REM e INT_SREM". RuleDivTermAddmaneja la corrección adicional, RuleSignDiv2- ¿ Qué ?(V + -1*(V s>> 31)) s>> 1en elV s/ 2, y RulePositiveDivLa lección 37 capturó Ghidra 12.1.4 en la lección 33's gradeEn el-O1, y la salida se lee uVar2 = param_1 / 10 - 6;La división está doblada.
La entrada a esas reglas es visible sin Java. el pypcode de venv 4.0.1 se eleva con la misma especificación SLEIGH que utiliza Ghidra, y code/fixtures/idioms-x86_64-O2.lifts.jsontiene el código P bruto para div10s: 112 entradas para siete instrucciones, de las cuales 7 son las instruccionesIMARKmarcadores que abren cada instrucción, entre ellos RAX = RAX * unique[95a00:8]después deunique[95a00:8] = sext(0x66666667)¿ Qué ?RCX = RCX >> unique[d1a00:4], y RAX = RAX s>> unique[c5c00:4]- El .shrqLa Comisión de Agricultura y Desarrollo de la Agricultura de la Comunidad Europea (CE)sarqlleva 40, y la mayoría de ellos cuentan las banderas.RuleDivOptcoincide con el multiplicador y los dos cambios en esta corriente, y la eliminación de código muerto elimina las banderas que nada lee.
Angr 10.0.3 se ejecutó en nuestro -O2objeto, y idioms-x86_64-O2.angr.jsonTiene su salida de decompiler para 20 funciones.div10u¿ Es verdad ?return a0 / 10;No dobla el formulario firmado: div10s¿ Es verdad ?v1 = a0 * 1717986919; return ((long long)(v1) >> 0x22) + (v1 >> 63);y mod10s¿ Es verdad ?a0 - (...) * 2 * 5- Para .div7uImprima .(a0 / 7 + ((a0 * 6 / 7 & 0xffffffff) >> 1 & 0xffffffff) & 0xffffffff) >> 2, donde los términos de fijación sobreviven como aritmética.mul9¿ Qué ?a0 * 9¿ Qué ?linear_switch¿ Qué ?(a0 < 7 ? a0 * 10 + 10 : 0xffffffff)¿ Qué ?clamp_max¿ Qué ?((int)a1 < (int)a0 ? a0 : a1)¿ Qué ?is_pos¿ Qué ?a0 > 0, y fill¿ Qué ?return memset(a0, 0, a1);Imprime .table_switchcomo carga .*(int *)(4 * a0 + 4195400)sin los valores, sum_ncon_mm_add_epi32y _mm_shuffle_epi32las características intrínsecas y tail_caller¿ Cómo ?tail_target(); return;Con el argumento perdido.digestLa mesa de salto se convierte engoto *((void *)(...))con la aritmética de la tabla escrita.
Radar2 6.2.4 y rizin 0.9.1, construidos desde la fuente en el caché de investigación, se ejecutaron en el mismo objeto.pdchuellasdiv10s¿ Cómo ?rax = rax * 0x66666667; rcx >>>= 0x3f; rax >>= 0x22; eax += ecx, pseudo-C de nivel de registro sin plegamiento, y table_switch¿ Cómo ?eax = dword [rcx + rax*4]con un RELOC 32 .rodatacomentario sobre el lea. rizin no tiene un descompilador incorporado; su aoiImprima el RzIL de la imulcomo un producto de 128 bits. lldb-1703.0.236.21 carga el objeto ELF y su disassemble -n div10sImprime las mismas siete instrucciones que llvm-objdump, porque ambos utilizan el desmonteador de LLVM. Capstone 5.0.7 decodifica los mismos bytes y informa grupos como rety mode64La documentación de IDA Pro y Binary Ninja no se ejecuta aquí. Su documentación describe las etapas en las que se produce dicha reescritura, la optimización del microcodo y el MLIL a HLIL pasa, y esta lección no hace ninguna afirmación sobre qué idiomas doblan.
El compilador Explorer es la herramienta para la dirección hacia adelante.idioms.c, elige una entrada de compilador como "x86-64 clang 18.1.0" o "ARM64 GCC 14.2.0", y establece las banderas. El servicio enumera alrededor de 1.050 entradas de compilador C y tiene una API: GET /api/compilers/cdevuelve los identificadores y POST /api/compiler/<id>/compileRetorna la asamblea como JSON. El artículo ACM Queue de Matt Godbolt de 2019 recorre la división, el conteo de población, los idiomas condicionales en cadena y la sumación con esta herramienta.-O2vectorization defectuosos de GCC 12 con la salida de clang en las fichas.
Construye el mismo
Ejecutar la demostración:
bashcd phases/13-tools-and-protocols/39-compiler-idioms-and-optimization
python3 code/main.pyLa salida es un informe JSON. Para cada una de las 18 ediciones enumera todas las funciones y sus idiomas, con tipo, forma de fuente, direcciones de evidencia, verificación y los campos de detalles.-O2En comparación-O3La comparación de ambas arquitecturas y launverified- ¿Qué es eso?
Forma esperada:
json{
"builds": {
"idioms-x86_64-O2": {
"arch": "x86_64", "level": "O2", "flags": "x86_64",
"functions": {
"div7u": [{"kind": "division", "source": "x / 7", "evidence": ["0x30", "0x32", "0x39", "0x3d", "0x3f", "0x41", "0x43"], "verified": true, "form": "magic multiply with add fix-up", "magic": "0x24924925", "effectiveMagic": "0x124924925", "shift": 35, "...": "..."}],
"table_switch": [{"kind": "switch", "source": "switch (k) as lookup table", "table": ".rodata+0x28", "entries": 7, "values": [10, 20, 30, 40, 50, 60, 71], "...": "..."}],
"sum_n": [{"kind": "vector loop", "lanes": 4, "accumulators": 2, "elementsPerIteration": 8, "...": "..."}]
}
}
},
"kindMatrix": {"idioms-x86_64-O2": {"division": 5, "modulo": 3, "switch": 6, "vector loop": 3, "...": "..."}},
"sameCodeAtO2AndO3": {"x86_64": {"identical": true}, "arm64": {"identical": false, "differences": ["..."]}},
"unverified": []
}Leer .code/main.pyen este orden:
parse_listing()vuelve elllvm-objdump -d -rel texto en funciones, instrucciones y reubicaciones adjuntas para ambas arquitecturas.ElfFileLeer secciones, símbolos y.rela.*las entradas ywords()Leer entradas de la mesa.magic_numbers()¿ Qué ?divisor_from_magic(), yevaluate_division()son la derivación Granlund-Montgomery hacia adelante, hacia atrás, y como la secuencia de instrucciones.parse_source()extrae las construcciones de las necesidades de verificación del archivo C.Evaluatores el pase simbólico bloque por bloque que registra nombres y espacios de pila.find_divisions()yfind_modulos()coincide con el multiplicador, el desplazamiento, el arreglado, el multiplicador y el poder de dos formas.find_switches()clasifica tablas de saltos, tablas de búsqueda, mapas lineales, seleccionar cadenas, árboles de comparación y comparar cadenas.find_vector_loops()¿ Qué ?find_unrolled_loops()¿ Qué ?find_tail_calls()¿ Qué ?find_inlined_calls()¿ Qué ?find_selects()¿ Qué ?find_memory_builtins()¿ Qué ?find_stack_protector()¿ Qué ?find_cet(), yfind_strength_reduction()cubre el resto.analyze_function()los corre en orden, alimentando el nombre del resultado de cada idioma en el siguiente pase de evaluador.
Hay tres órdenes más.python3 code/main.py scan FILE.objdump.txt [FUNCTION]ejecuta el reconocedor en una lista, con el objeto y el archivo C correspondiente que se encuentra al lado. python3 code/main.py magic DIVISOR [signed]Imprime el multiplicador, el cambio, el error y si la identidad se mantiene en los valores de borde. python3 code/main.py figure-dataImprime los datos que la figura incorpora.
Usalo
Utilice el reconocedor en este orden en un nuevo objeto que construyó.
- Captura .
llvm-objdump -d -rEl reconocedor lee la arquitectura desde el encabezado de la lista. - - ¿ Qué ?
scanen la lista y leer elunverifiedCada uno es una construcción que el analizador de origen no vio o una forma que el reconocedor ha interpretado mal, y ambos merecen una mirada a las direcciones de la evidencia. - Para cada división, lee el
algorithmcampo. ¿Cuándo?clangMatchesAlgorithmSi es falso, el compilador tenía una razón que aún no sabes; escribe las constantes. - Para cada interruptor, comparar los
loweringUna tabla que se convierte en un árbol, o un árbol que se convierte en una tabla, marca un umbral que se puede nombrar a partir de la fuente LLVM. - Para cada bucle, lee.
elementsPerIterationyscalarRemainderLoop, entonces confirme que los dos bordes de atrás de la lección 33 informes son un bucle de fuente. - ejecuta un decompiler en el mismo objeto, cita su texto junto a cada idioma, y graba cuáles doblaron. Ese registro es la línea de base para la lección 45.
Laboratorio interactivo
Laboratorio A: derivar un número mágico, y luego encontrarlo
Desde elcodedirectorio, Python abierto:
bashcd phases/13-tools-and-protocols/39-compiler-idioms-and-optimization/code
python3 -q- ¿Qué quieres decir ?
pythonfrom main import *
magic_numbers(10), magic_numbers(10, signed=True)
magic_numbers(7), magic_numbers(7, signed=True)
divisor_from_magic(0x66666667, 34, signed=True), divisor_from_magic(0x66666667, 33, signed=True)Computaciónceil(2^35 / 10)en papel antes de la primera llamada. Luego añadir una función unsigned div13(unsigned x) { return x / 13; }a una copia de idioms.c, construye en -O2con el mando en build_fixtures.sh, y comprobar la constante en la lista contra magic_numbers(13)¿ Qué ?
Laboratorio B: la mesa y el árbol
pythonlisting = load_listing("idioms-x86_64-O2.objdump.txt")
elf = load_elf("idioms-x86_64-O2.o")
sources = load_source("idioms.c")
[i.to_dict() for i in analyze_function(listing, "table_switch", elf, sources)]
[i.to_dict() for i in analyze_function(listing, "sparse_switch", elf, sources)]
elf.words(".rodata", 0x28, 7)Cambiar71De vuelta a70en una copia de table_switch, reconstruir en-O2, y ver la tabla de búsqueda se convierte en un mapa lineal. Luego cambiar un valor de caso a 1000 y ver que se mantiene unArrayKind¿Qué es?
Laboratorio C: el bucle en tres configuraciones
pythonfor name in ("idioms-x86_64-O1", "idioms-x86_64-O2", "idioms-x86_64-novec-O2"):
print(name, [i.source for i in analyze_function(load_listing(name + ".objdump.txt"), "sum_n", load_elf(name + ".o"), sources)])Dibujar el gráfico de flujo de control de sum_nEn el-O2de mano, marque el guard, la máscara, el cuerpo vectorial, la reducción y el ciclo restante, y compare con el detaillos campos.
Laboratorio D: el cuotiente compartido
pythonfor name in ("idioms-x86_64-O1", "idioms-arm64-O1"):
print([(i.kind, i.source, i.detail.get("sharedQuotient")) for i in analyze_function(load_listing(name + ".objdump.txt"), "digest", load_elf(name + ".o"), sources)])Lea el -O1lista de digesty encontrar las dos instrucciones que combinan el cuotiente con su señal de fijación en x86-64.
Laboratorio de práctica
Añade trucos a los accesorios y amplía el reconocedor.
Requisitos:
- Escriba .
popcount¿ Qué ?ctz, yrotlLas funciones de la construcción de los CCG y un cambio o rotación, construirlos en-O2con y sin-mpopcnt -mbmi, y comprometer los objetos con sus listas. - Reconozca el
0x55555555¿ Qué ?0x33333333¿ Qué ?0x0f0f0f0f¿ Qué ?0x01010101Secuencia de recuento de población queTargetLowering::expandCTPOPemitir, y reportarlo comopopcountcon las cuatro constantes como evidencia. - Reconocerlo
rep bsflcomo eltzcnt- O - - ¿Qué es eso?bsfcodificación yroll %clcomo el girar, y descifrar ambos a la construcción. - Añadir los nuevos tipos a
parse_source()la verificación, por lo que unapopcountEl idioma sólo se verifica cuando la fuente llama__builtin_popcount¿ Qué ? - Añadir una prueba por idioma que lee las constantes de la lista de fijación.
El éxito significapython3 code/main.pyLas nuevas especies se han introducido en la lista de los Estados miembros.verified: trueEn ambas construcciones, y la suite todavía informa de un vacíounverified- ¿Qué es eso?
Lección Artefacto
Esta lección incluye outputs/skill-compiler-idioms-and-optimization.md. Utilice para convertir una lista, un objeto y su fuente en un informe de idioma verificado con una comparación de descompilador. Requiere direcciones de evidencia para cada afirmación, la derivación de número mágico para cada división, el tipo de bajada y la fuente de entrada para cada interruptor, y una línea de descompilador citada para cada idioma.
Verifique el hecho
Ejecutar la demostración y la suite determinista:
bashcd phases/13-tools-and-protocols/39-compiler-idioms-and-optimization
python3 code/main.py
python3 -m unittest discover -s code/tests -vLa verificación deberá demostrar:
- el parser de lista lee 25 funciones de la
-O2Objeto, mantiene los bytes, y se adhiereR_X86_64_PC32 .rodata+0x24a laleaqEn eltable_switch - El lector de ELF devuelve
[10, 20, 30, 40, 50, 60, 71]de la.rodata+0x28y diez reubicaciones en.rela.rodata magic_numbersretorno0xcccccccdcon el turno 35,0x66666667con 34,0x124924925con 35 y el formulario adicional,0x55555556con 32, y0xaaaaaaabcon 33- la identidad mantiene los valores de borde de 32 bits para cada divisor de 2 a 64 que no sea un poder de dos, firmado y no firmado
divisor_from_magicrecupera 10, 5, 7 y 3 de sus pares y rechaza un par equivocado- el computo de la secuencia de fijación de la adición
n // 7exactamente - Las cuatro divisiones x86-64 informan el multiplicador derecho, el cambio, el divisor y la forma, y las cuatro divisiones AArch64 decodifican
movymovk -O0usosdivlyidivl¿ Qué ?-O0 -fast-isel=falseutiliza el multiplicador, ydigestEn el-O0mezcla las dos- el modulo se reconoce como el multiplicador de la vuelta,
msub, potencia de dos,sdivAdemásmulAdemássubs, y el hardwareidivl - un cuotiente compartido mantiene la división y el modulo en
-O1 - Las cinco reducciones se clasifican en
-O2En ambas arquitecturas, con la fórmula, los valores, los objetivos, los pivotes y la expresión seleccionada -O0los interruptores son tablas de salto en x86-64 y comparar cadenas en AArch64- el
digestel switch es una tabla de saltos, luego una tabla de búsqueda con factor 16 doblado, luego una tabla de saltos con cuatro objetivos distintos en x86-64, y un árbol de comparación con el test de rango 9 a 10 en AArch64 -fno-jump-tablesproduce árboles de comparación, y el caso 16dispatchlee sus objetivos de las reubicaciones en x86-64 y de una tabla de byte comprimida en AArch64- El bucle vectorial informa 4 carriles, 2 acumuladores, 8 elementos por iteración, la máscara, la guardia, la reducción y el bucle restante
- Los bucles de trayecto fijo están totalmente vectorizados o completamente desarrollados, y
-fno-vectorizedesdoblaciones a partir de 8 -O1no tiene vectorización,-O2y-O3son idénticos en x86-64, y en AArch64 sólo difieren encall_switch, donde-O3Tiene 17 instrucciones y-O2tiene 18-fno-builtintransforma el bucle de la tienda en un bucle de la tienda vectorizado y mantienememcpyuna llamada- Las llamadas de cola, el
memsetla copia en línea de 16 bytes, y el-O0se reconocen las copias de dos movimientos - La calle en línea se encuentra en
-O1por su forma de cuerpo - selecciona el decodificación a
a > b ? a : b¿ Qué ?-x < 0 ? x : -x¿ Qué ?(x > 0), ysum % 10 < 5 ? sum % 10 : 5 - los informes de protección de la pila
%fs:0x28, la ranura, y__stack_chk_fail, yendbr64se informa sobre cada función de la estructura de la CET - el parser de origen lee los parámetros de función-pointer, los casos de interruptores, las llamadas y los nombres del acumulador
- Cada idioma en cada construcción se verifica y las matrices de tipo coinciden
- La figura interactiva incorpora los mismos datos que el análisis produce
Modos de falla de producción
| Failure | What the weak tool reports | What the analysis must prove |
|---|---|---|
| Multiply by a large odd constant | "multiplies by 1717986919" | The constant is ceil(2^s / d) for one d, the error satisfies the bound, and the shift is the smallest valid one |
| Add fix-up after a 32-bit shift | Five arithmetic instructions | The multiplier is 33 bits wide and the tail is the add form of the same method |
| Same multiplier, different shift | The same divisor | The shift selects the divisor: 34 is 10, 33 is 5 |
| Power-of-two signed modulo | "adds 7, masks, subtracts" | The bias rounds toward zero and the result is x % 8 |
Indexed load from .rodata | "a jump table" | A lookup table of values, with entries read from the bytes at addend plus 4 |
Affine arithmetic and a cmov | "computes 10k + 10" | A switch lowered as a linear map, verified against every source case |
| Compare chain with ordered splits | "a sequence of ifs" | A binary search over the case constants with the pivots named |
| Backward branch to a default block | "a loop" | The target leaves the region with an unconditional jump; it is a shared tail |
| Two back edges in one function | "two loops" | One source loop: the vector body and its scalar remainder |
jmp memset@PLT with no ret | "the function has no return" | A tail call that returns through the callee, replacing a store loop |
| Callee missing from the call graph | "never called" | Inlined: the callee's body shape appears in the caller |
%fs:0x28 load and compare | "reads thread-local data" | The stack protector canary, with the fail path to __stack_chk_fail |
| Four odd bytes at function start | "data before the function" | endbr64, the CET landing pad |
| Decompiler shows raw arithmetic | "the tool is wrong" | The tool did not apply a fold; the derivation gives the source form and the quoted output records the gap |
Regla de funcionamiento
Compare la forma, ejecuta la derivación hacia atrás y escribe las constantes que lo prueban. Informar cada idioma con sus direcciones de evidencia y su forma de fuente decodificada, verificar cada uno contra la fuente que compila, y citar lo que cada decompiler impreso para los mismos bytes.
Leer más
- Granlund, Montgomery, "Division by Invariant Integers using Multiplication" (PLDI 1994)
- Warren, Hacker's Delight, 2nd edition, Chapter 10: Integer Division By Constants
- Lemire, Kaser, Kurz, "Faster Remainder by Direct Computation" (2019)
- libdivide, header-only division by runtime constants
- LLVM
DivisionByConstantInfo.cpp - LLVM
SwitchLoweringUtils.cppyTargetLoweringBase.cpp - LLVM
SimplifyCFG.cpp,simplifySwitchLookup - LLVM Auto-Vectorization documentationy LLVM 3.4 release notes
- LLVM
LoopIdiomRecognize.cpp - LLVM
X86CmovConversion.cpp - LLVM
StackProtector.cppyX86ISelLoweringCall.cpp,getIRStackGuard - glibc
sysdeps/x86_64/nptl/tls.h,tcbhead_t - GCC
tree-switch-conversion.cc¿ Qué ?GCC Optimize Options, y GCC 12 release notes - Ghidra decompiler
ruleaction.cc - angr decompiler documentation
- radare2 book: decompilers
- Compiler Explorer API
- Godbolt, "Optimizations in C++ Compilers: A practical journey", ACM Queue 17(5), 2019
- Microsoft,
/GS(Buffer Security Check)
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.