programma x86 per eseguire un esponente
Per continuità, la mia ultima domanda era con l'aggiunta di numeri insieme a una chiamata di funzione: Esempio di programma x86-64 . In questo ho cercato di applicare le lezioni indicate nella risposta precedente (allineamento dello stack, utilizzando edxinvece di rdxdove si adatteranno i risultati). Ho provato a commentare in linea con il codice:
# 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
Ho alcune domande specifiche sul codice:
- Sta facendo un raw
push/popcomune? O è la convenzione sempre di farepush rbpmov rsp rbp...pop rbp. Perché un metodo è preferito rispetto all'altro? - E se volessi memorizzare il risultato del primo calcolo in un registro: ci sono registri di cui è garantito il mantenimento tra le chiamate di funzione? O è per questo che il
push...popmetodo viene utilizzato così tanto? - Infine, il mio
expsembra ok? Sembra un po 'strano avere tre etichette in quello che equivale a una funzione. Come potrebbe essere migliorato?
Risposte
Non sono un esperto, ma che diamine, ecco un commento sulla recensione:
# needs to be 16-byte aligned before the next function call so do two pushes
pushq $0
pushq %rax
Questo commento è buono, ma lo riformulerei. Due push di otto byte fanno 16 byte e non cambiano l'allineamento dello stack. Quindi deduco che una di queste spinte sia significativa e l'altra insignificante , ma il tuo commento non mi dice quale sia! Quindi potresti dire invece
# one extra push to preserve 16-byte stack alignment
pushq $0
# push the result of `exp`
pushq %rax
È possibile ridurre il codice generato eliminando la costante insignificante $0:
# push the result of `exp`, plus one extra push to preserve 16-byte stack alignment
pushq %rax
pushq %rax
Ora il lettore non ha nemmeno bisogno di capire quale spinta è quella significativa, perché entrambe le spinte fanno la stessa cosa!
Ma perché è importante preservare l'allineamento a 16 byte sulle chiamate? Non è un requisito della macchina . Sembra che tu stia cercando di seguire qualche ABI specifico , come forse per l'interoperabilità con C o C ++. La tua documentazione esterna dovrebbe essere più chiara su ciò che ABI stai cercando di seguire.
E poi, se si sta cercando di interagire con il codice C, si potrebbe migliorare il codice indicando quali delle sue etichette sono da intendersi come entry point esterni e quali sono le etichette locali appena interne. Sembra che tu abbia intenzione expdi essere chiamato da un altro codice - è un punto di ingresso - ma ad esempio exp_opnon è richiamabile, ed exp_retè tecnicamente richiamabile ma funge solo da no-op. Potresti contrassegnarli in qualche modo come "dettagli di implementazione locale, non per consumo esterno".
Sì, tecnicamente lo fai già esportando .globl _starte non .globl exp, ma c'è ancora una grande differenza tra la funzione richiamabile expe l'etichetta locale exp_opche non si riflette nel tuo schema di denominazione. Se lo stessi facendo, aggiungerei .globl expe rinominerei exp_op, exp_retin qualcosa come Lexp1, Lexp2o L1_looptop, L2_loopend.