programa x86 para hacer un exponente

Sep 11 2020

Para continuar, mi última pregunta fue sumar números junto con una llamada de función: Ejemplo de programa x86-64 . En este, intenté aplicar las lecciones señaladas en la respuesta anterior (alineación de la pila, usando en edxlugar de rdxdonde encajarán los resultados). Intenté comentar en línea con el código:

# We are going to calculate 7^2 + 2^4 = 49 + 16 = 65
# The exponent function supports whole numbers (long) >= 0

.section .data
base_1: .long   7
exp_1:  .long   2
base_2: .long   2
exp_2:  .long   4

.section .text
.globl _start
_start:


    # We will do the first function call, 7^2
    mov base_1(%rip),   %edi
    mov exp_1(%rip),    %esi
    call exp

    # needs to be 16-byte aligned before the next function call so do two pushes
    pushq $0
    pushq %rax

    # Now do the second function call
    mov base_2(%rip),   %edi
    mov exp_2(%rip),    %esi
    call exp

    # We have the return value in %eax so let's add this with our previous function's value
    popq %rdi
    
    add %eax,   %edi
    mov $60,    %eax
    syscall

exp:
    # Initialize %eax to 1
    mov $1,     %eax

exp_op:
    cmp $0,     %esi
    je exp_ret
    imul %edi,  %eax
    dec %esi
    jmp exp_op

exp_ret:
    ret

Tengo algunas preguntas específicas sobre el código:

  • ¿Está haciendo un raw push/ popcommon? O es la convención siempre que hacer push rbp mov rsp rbp... pop rbp. ¿Por qué se prefiere un método sobre el otro?
  • ¿Qué pasa si quisiera almacenar el resultado del primer cálculo en un registro: hay registros que se garantice que se conservarán entre las llamadas a funciones? ¿O es por eso push...popque se usa tanto el método?
  • Finalmente, ¿ expparece estar bien? Se siente un poco extraño tener tres etiquetas en lo que equivale a una función. ¿Cómo podría mejorarse eso?

Respuestas

3 Quuxplusone Sep 11 2020 at 13:45

No soy un experto, pero qué diablos, aquí hay un comentario de revisión:

# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0
pushq %rax

Este comentario es bueno, pero lo reformularía. Dos empujes de ocho bytes generan 16 bytes y no cambian la alineación de la pila. Por lo tanto, infiero que uno de estos empujones es significativo y el otro es insignificante , ¡pero su comentario no me dice cuál es cuál! Entonces podrías decir en su lugar

# one extra push to preserve 16-byte stack alignment
pushq $0

# push the result of `exp`
pushq %rax

Puede hacer que el código generado sea más pequeño eliminando la constante insignificante $0:

# push the result of `exp`, plus one extra push to preserve 16-byte stack alignment
pushq %rax
pushq %rax

Ahora el lector ni siquiera necesita averiguar qué empujón es el significativo, ¡porque ambos empujes hacen lo mismo!


Pero, ¿por qué es importante preservar la alineación de 16 bytes en las llamadas? Eso no es un requisito de la máquina . Parece que intenta seguir alguna ABI específica , como quizás para la interoperabilidad con C o C ++. Su documentación externa debería ser más clara sobre qué ABI está tratando de seguir.

Y luego, si está intentando interoperar con el código C, podría mejorar su código indicando cuáles de sus etiquetas son puntos de entrada externos y cuáles son solo etiquetas locales internas. Parece que tiene la intención expde que lo llamen desde otro código, es un punto de entrada, pero, por ejemplo, exp_opno se puede llamar y exp_rettécnicamente se puede llamar, pero solo actúa como una operación no operativa . Puede marcarlos de alguna manera como "detalles de implementación local, no para consumo externo".

Sí, técnicamente ya lo hace exportando .globl _starty no .globl exp, pero todavía hay una gran diferencia entre la función invocable expy la etiqueta local exp_opque no se refleja en su esquema de nombres. Si estuviera haciendo esto, agregaría .globl expy cambiaría el nombre exp_op, exp_reta algo como Lexp1, Lexp2o L1_looptop, L2_loopend.