Rispondi 
[ASM & HEX] Aula di skyos, Tony26, Wendrew, Chimchar101 - Prof. Bonnox
Bonnox
Signore del Tempo un po' imbranato
*****
Online

Messaggi: 1,751
Registrato: 06-07-2014
Reputazione: 7
Mi Piace Ricevuti: 361
PokéMoney: 1,871.50₱
Messaggio: #109
RE: Aula di skyos, Tony26, Wendrew, Chimchar101 - Prof. Bonnox
la quick guide, thumb ref (o come si chiama)

contiene la sintassi

edit

una cosa che non ho specificato: ignora ciò che è della "v5", in quanto l'architettura del Gba , nonostante il proc abbia un 7 nel nome, è la 4.

per curiosità, i cellulari android fino a prima dei 64 bit erano v7. i 64 bit sono v8.
(Questo messaggio è stato modificato l'ultima volta il: 25-10-2017 08:21 AM da Bonnox.)
25-10-2017 08:07 AM
Cerca Cita
Bonnox
Signore del Tempo un po' imbranato
*****
Online

Messaggi: 1,751
Registrato: 06-07-2014
Reputazione: 7
Mi Piace Ricevuti: 361
PokéMoney: 1,871.50₱
Messaggio: #110
RE: Aula di skyos, Tony26, Wendrew, Chimchar101 - Prof. Bonnox


scusa se questa lezione contiene pochi esempi, ma ho deciso di postarla perchè mi ero liberato da alcuni impegni, ma non ho comunque abbastanza tempo per includere nuovi esempi completamente corretti. se te ne serviranno altri chiedili pure, solo fai attenzione che non arriveranno immediatamente.

nota: ho spiegato lo swi senza averlo spiegato nella lez precedente, ecco qua un riassunto


ESERCIZI 4
ASM BASE




in questa sede trasporremo in ASM gli algoritmi visti nelle precedenti lezioni, con lo scopo di abituarci alla sintassi ^^

ho preso alcuni degli esercizi visti nella lezione di programmazione, in modo da poter riflettere sulle differenze, più alcuni presi da internet sul C, o inventati da me ^^

innanzitutto se avete dei dubbi sulla sintassi consultate pure il PDF che vi ho fato scaricare all'inizio.
esso ha 3 aree: una zona in cui viene presentato il comando in modo molto sintetico, una che illustra la sintassi di ogni variante di ogni comando, e una che mostra come sono fisicamente realizzati gli opcodes. Ora vi esporrò brevemente i punti salienti della sintassi.

nota: le virgolette sono per delimitare la citazione. non fanno parte della sintassi.
  • "[RX]" indica di indirizzare l'operazione all'offset di memoria contenuto nel registro.
    dunque, l'istruzione

    "LDR RX, [RY]"

    caricherà in RX i 4 byte presenti all'indirizzo di memoria contenuto in RY.

    Invece "[RX, RY]"

    oppure

    "[RX, #valore]"

    significa indirizzare l'operazione all'indirizzo contenuto da RX + RY o RX + valore)

    .
  • le etichette servono per distinguere parti di programma, come le funzioni, e servono come destinazione dei salti. per dichiarare un'etichetta basta metterne il nome seguito da due punti, così:

    "main:"
    .
  • quando iniziate una routine ricordatevi SEMPRE di allinearla. per farlo si usa:

    ".align X, 0xY"

    questa è una direttiva al compilatore, ovvero delle istruzioni che noi gli diamo per informarlo di qualcosa prima di procedere a compilare. le direttive iniziano con il punto. in particolare align indica di allineare il compilato a partire da 2^X byte, riempiendo lo spazio intermedio con il byte Y. Di solito questo parametro si omette.
    L'align è anche usato sui dati, in quanto se non fossero allineati (specialmente le strutture), sarebbe un casino assicurato nella gestione della memoria. Inoltre alcuni opcodes, se avete letto la documentazione, hanno un offset di reperimento delle informazioni che va a blocchi di 4 byte.

    Dunque la pratica, abbastanza diffusa, di usare

    "LDR RX, .pointer

    [...]

    .align 2
    .pointer:
    .word 0xqualcosa"

    nasce dal fatto che l'istruzione LDR ha nei suoi campi (a livello fisico, potete vederlo nella thumbref) un valore che indica "quante word in là" andare a prendere il dato. (ricordando che una word è lunga 4 bytes, e 2^2 fa 4, si spiega il perchè si mette sempre align 2)

    In realtà sarebbe meglio fare così:

    "LDR RX, valore

    [...]

    .align 2
    valore: .word 0xqualcosa".


    Se proprio non voleste niente sotto, un'altra soluzione è

    "LDR RX, =0xqualcosa"

    in questo modo caricherete subito il valore.
    Oppure potreste anche fare di meglio. quest'ultimo metodo è parecchio scomodo, in quanto incline agli errori e non centralizzato: se usate un valore molte volte e ad un tratto lo volete sostituire, dovrete cercarlo per tutto il sorgente. Leggendo la guida dell'assemblatoreho scoperto questa nuova direttiva chiamata "equ". Svolge un compito simile al #define del C, ovvero sostituisce tutte le occorrenze di un valore nel testo, gestendo automaticamente la questione degli allineamenti. In questo modo scriverete una sola volta il valore, ma potrete usarlo sempre, unendo così i vantaggi dei due precedenti metodi (centralizzazione del valore e praticità). ecco un esempio:

    ".equ alias, 0xqualcosa

    LDR RX, =alias"

    dove alias è una qualunque parola inventata da noi. la direttiva può essere inserita ovunque nel sorgente, perciò prestate attenzione, anche se vi potrebbe sembrare un vantaggio, in realtà contribuisce a rendere meno leggibile il codice. Sarà bene adottare un proprio stile, ovvero decidere se mettere tutte le dichiarazioni di costanti all'inizio o alla fine del file: personalmente ritengo che dichiarare una costante al momento dell'uso renda poco leggibile il codice.

    Un'altra direttiva è .include, che unisce un file in quello corrente, a compile-time:
    supponete di aver creato un file con delle funzioni che usate spesso. potrete "includere" il file nel vostro nuovo sorgente (di solito i file di sorgente assembly si chiamano *.ASM o *.S ) in modo da poterle usare senza doverle riscrivere. è una tecnica molto comoda, in quanto consente di risparmiare tempo. Il meccanismo è lo stesso dei blocchi più grossi nei diagrammi di flusso, che contengono un'insieme di operazioni già definite da trattarsi come una atomica. la sintassi è questa:

    Citazione:.include "path"

    dove path è il percorso al vostro file. le virgolette vanno messe.
    .
  • per inserire un immediato, si usa "#"

    Un immediato è un valore da usare nell'operazione, inserito nell'istruzione stessa, al posto di usare un registro. in genere se avete valori, conosciuti prima dell'esecuzione, inferiori o uguali a 255 si possono tranquillamente mettere come immediati.
    esempio:

    "MOV R1, #0"
    .
  • potete inserire un commento con la chiocciola, e quel'intera riga, a partire dal carattere chiocciola, non verrà considerata durante la compilazione. esempio:

    "LDR R0, 0x083E6E50 @carico l'offset"

    Consiglio di inserire sempre qualche commento, anche se all'inizio possa sembrare inutile, perchè potrebbe capitarvi di non riuscire a capire il vostro stesso codice! Questo perchè è complicato, e se non lo guardate per un po' vi dimenticherete. Credeteci. Poi se lo dovrete far leggere ad altri è ancora più "moralmente obbligatorio": è un vero atto di cortesia, e se non lo fate rischierete il linciaggio!

ed ecco alcuni swi che potrebbero tornarvi utili (#se leggete "dsi" o "nds" per favore ignoratelo):



noi (io) negli esercizi useremo l'indirizzo 02000000 come scambio di informazioni tra script e routine; potrete facilmente cambiarlo.

ESERCIZIO: dato un numero, stampa (o ritorna) il fattoriale

Codice:
    .THUMB
    .ALIGN 2, 0x0
    
    @; RO: OFFSET MEM
    @; R1: VALORE
    @; R2: DECREMENTO
    
    PUSH {R0-R4,LR}
        MOV R0, #1 @;INIZIALIZZO REGISTRO: 2000000
        LSL R0, #0X19
        
        LDRH R1, [R0] @; LEGGO VALORE INPUT
        
        MOV R2, R1
        
        fattoriale_ciclo_calcolo:
        
            SUB R2, R2, #1    @; ALGORITMO ITERATIVO DEL FATTORIALE
            
            MUL R1, R2    
            
            CMP R2, #1
        BHI fattoriale_ciclo_calcolo
        
        STR R1, [R0]        @;FORNISCO IL RISULTATO
        
    POP {R0-R4,PC}
tutto quello che abbiamo fatto è stato inizializzare i valori ed eseguire un semplice ciclo in cui moltiplichiamo il numero per sè stesso.

ESERCIZIO: dati n numeri, calcolare la media

avvertenza: ho usato il byte a 200000 per indicare il numero di elementi, che ho messo in un vettore (per complicare un pochino le cose, se no non c'è gusto XD - no scherzo in realtà perchè così avete già in mente come trattare gli altri problemi complicati sui vettori) che compare subito dopo.

ecco, ogni tanto provate a fare un riassunto del contenuto dei registri, come ho fato io due volte qua sotto (le righe lunghe), così da non confondervi!
(consiglio di usare programmers notepad o un altro editor di testo potenziato rispetto allo squallore di blocco note, per avere la sintassi colorata)

Codice:
.thumb
.align 2, 0x0
.equ scambio, 0x2000000

push {r0-r4, lr}
    
    @; r0: accesso vettore, r1: numero elementi r2: somma temporanea r3: elemento letto corrente r4: indice del vettore
    
    inizializzazione:
    mov r2, #0
    mov r4, #0
    
    ldr r1, =scambio
    mov r0, r1            @; copio
    ldrb r1, [r1]        @; leggo il numero di elementi
    add r0, r0, #1        @; mi posiziono sul vettore
    
    ciclo:
    
    ldrb r3, [r0, r4]         @; accesso tramite indicizzazione
    add r2, r2, r3
    add r4, r4, #1
    cmp r1, r4
    bhi ciclo
    
    @; r0: somma temporanea r1: numero elementi r2: / r3: /  r4: memorizzazione
    
    mov r4, r0            @; preparo la divisione
    sub r4, r4, #1
    mov r0, r2
    mov r2, #0
    mov r3, #0
    
    swi 0x6
    
    strb r3, [r4]        @; comunicazione del risultato
    
    fine:
pop {r0-r4, pc}

notate che per entrambi gli esempi ho scelto di memorizzare il risultato nella memoria RAM per semplicità. Se però le funzioni fossero state inserite in un contesto più ampio, sarebbe stato preferibile usare un valore di ritorno. come si fa? bisogna semplicemente non pushare un (o più) registro, che useremo per contenere il risultato. un esempio di questa tecnica è lo SWI, come abbiamo visto nella divisione, che ritorna qualcosa nei registri. ma noi come possiamo?
generalizzando,
Codice:
PUSH {RY-RZ, LR}

@ fai qualcosa

MOV RX, XXX @ memorizza in qualche modo il risultato

POP {RY-RZ,PC}


con, generalmente, X < Y < Z (numeri sempre a vostra scelta, ma meglio se sensata)
in questo modo RX sarà in comune sia alla funzione corrente che a quella chiamante. poi quello che la chiamante farà non è affar della corrente.
nella seconda tipologia di esercizi userò il valore di ritorno, dunque attenti!

#sposare su programmazione
una funzione si dice ricorsiva se durante l'esecuzione chiama sè stessa o chiama una funzione che chiama sè stessa. equivalentemente, in matematica, una funzione è ricorsiva se è definita in termini di sè stessa.

è importante che esista una condizione di base che permette di non chiamare la funzione, altrimenti si genererebbe un ciclo infinito.

pensiamo al fattoriale di n, ovvero la produttoria di numeri che vanno da 1 a n.
è possibile usare l'algoritmo iterativo (anzi c'è un teorema che dice che qualunque problema è risolvibile tramite le strutture fonfamentali), oppure quello ricorsivo.
in questo caso, il fattoriale di n (scritto n!) possiamo vederlo come n*(n-1)!.


http://www.pokemonhacking.it/attachment.php?aid=441

è proprio la condizione di base che ci permette di uscire dal calcolo.


Che succede se finite i registri come variabili?
abbiamo essenzialmente tre alternative: usare la memoria, lo stack o i registri alti.
per usare la memoria non ci vuole molto, basta scrivere sulla 02 qualche valore da andare a prendere dopo. ovviamente però non dovrete dimenticare dove avete messo i dati! per questo è meglio organizzare la cosa come un vettore di int (il tipo di grandezza massima) allineato, e il cui indirizzo è mantenuto in un rgistro. ora, con semplici immediati (ricordate equ), potrete accedere a qualsiasi cosa vogliate (dubito che abbiate più di 255 variabili), tramite l'indirizzamento scostato (quello con il [RX, #numero]).
la seconda alternativa invece è più integrata nel sistema, ma più delicata.
infatti potrete pushare (accatastare) sullo stack un qualsiasi valore, caricare il nuovo dato in un registro, fare l'operazione e poi poppare (togliere) il valore. RICORDATE CHE ALL'INIZIO E ALLA FINE DEL PROGRAMMA ASM LO STACK DEVE ESSERE NELLO STESSO STATO! ovvero, POPPATE SEMPRE CIO' CHE PUSHATE!
questo perchè altrimenti il programma esterno (la ROM) si ritroverà dati non coerenti e anrà in crash (nel migliore dei casi sarà tutto glitchato).
Poi voi in mezzo alla vostra pila personale potrete fare tutte le vostre porcherie (so che le farete, anche se dovrebbe essere vietato da un'autorità universale), come ad esempio prendere un valore dal mezzo (se lo fate con una pila di piatti vedete che vi succede... XD)... l'importante però è che alla fine della vostra funzione lo stack sia come l'avete trovato.
la terza soluzione, che è un po' scontata, prevede di utilizzare i registri da 8 a 12. il problema è che pochissimi opcodes li trattano, perciò sono scomodi. e comunque dovrete depositarne il contenuto con uno dei metodi di prima ^^".



ora c'è una nuova tipologia di esercizio: io vi darò una funzione, e dovrete rispondere cosa fanno. le prime sono di esempio.

1)
avete due files, quello del programma e quello di utility.


Codice:
.thumb
.equ input, 0x2000000
.ascii "inizio programma" @; scrive sul file binario
.align 4, 0x0
PUSH {R0-R4, LR}
    
    main:
    
        LDR R2, =input
        LDR R4, [R2]
        MOV R0, R4
        MOV R1, #4
        BL utility_main
        CMP R0, #0
        BNE no
        MOV R0, R4 @; riprendo il dato parcheggiato
        MOV R1, #100
        BL utility_main
        CMP R0, #0
        BNE si
        MOV R0, R4
        MOV R1, #25
        LSL R1, #4
        BL utility_main
        CMP R0, #0
        BNE no
        
        si:
            MOV R0, #1
            LDR R2, =input
            STRB R0, [R2]
        b return
        
        no:
            MOV R0, #0
            LDR R2, =input
            STRB R0, [R2]
    return:
POP {R0-R4, PC}

.include "lib/utility.s" @; entro in una cartella per prendere l'utility

@; l'ho messo in fondo perchè il compilatore non ha capacità di riconoscere il programma prinicipale, compila solo
@; nell'ordine in cui trova il codice, perciò così abbiamo subito il nostro programma all'inizio del file binario
@; (utile per fare eventualmente un callasm); mentre le librerie non ci interessano e rimarranno confinate in fondo.

Codice:
.THUMB
.ALIGN 4, 0xFF
    utility_main:
    PUSH {R2-R6,LR}
    
    MOV R2, #0
    MOV R3, #0
    
    MOV R5, R0
    MOV R4, R1

    SWI #0X6
    
    CMP R1, R4
    BHI utility_correggi
    
    B utility_fine
    
    utility_correggi:
    NEG R1, R1
    SUB R1, R1, #1
    utility_fine:
    MOV R0, R1
    
    POP {R2-R6,PC}

innanzitutto scopriamo cosa fa l'utiliti. R0 e R1 sono parametri, in quanto usati senza essere pushati. notiamo inoltre che usa lo swi del div, dunque sarà qualcosa attinente alla divisione. noi sappiamo l'output del div, e vediamo che la routine è interessata al R1, ovvero il modulo. anzi, la funzione ritorna proprio quello in R0. ma cos'è quel pezzo prima della fine? se il modulo è maggiore del denominatore (#per una questione che mi son dimenticato di spiegare, si chiama "complemento a due", se volete cercarla su internet o se no mandatemi un messaggio), significa che il numero è necessariamente negativo, dunque lo nega e sottrae 1, ovvero ottiene lo stesso numero positivo.
dunque la routine restituisce il valore assoluto del modulo tra R0 e R1.

cosa fa il file principale?
-controlla se un numero è divisibile per 4
--se non è divisibile restituisce 0
--altrimenti
---controlla se è divisibile per 100
----se no, restituisce 1
----altrimenti
-----controlla se è divisibile per 400 (25*2^4)
-----restituisce la stessa verità del'ultimo controllo.

questo è palesemente l'algoritmo di controllo della bisestilità di un anno.
la comunicazione con l'esterno avviene tramite RAM e non tramite ritorno perchè è stata pensata per essere usata in uno script.

consiglio: usate le librerie, aiuteranno a scrivere molto meno!
in particolare, come esercizio, potreste fare dei "wrapper" per le funzioni swi, come la precedente, dato che fanno spesso e volentieri macello con i registri, e potreste trovarvi in difficoltà. dunque con una routine come questa annullerete l'effetto caos e anzi ritornerete in modo elegante ciò che vi serve.


compito

vi consiglio di trovare prima una soluzione con gli schemi a blocchi. e dato che non sono necessarie molte variabili, vi consiglio anzi di tradurre 1:1 le singole strutture in blocchi di codice assemly (ad esempio tutte le sequenze in una sequenza asm), e poi di etichettarli. così potrete facilmente creare il controllo del flusso di esecuzione.


- verificare se un vettore è ordinato
- verificare se un vettore contiene elementi tutti uguali
- bonus: compattare un vettore (eliminare i valori duplicati e quelli pari a 0)

consigli:
- inutile dire che i cicli saranno d'importanza fondamentale.
- non avete limitazioni oltre alle richieste. (a buon intenditor... vabbè ve la spiego: non vi ho detto di che tipo è il vettore, dunque sceglietene uno comodo... vabbè ve lo dico ancora: di sicuro non sarà il tipo booleano o short, ma un tipo che sfrutta appieno la tecnologia del processore...)
- per l'ultimo, prima create una funzione per individuare gli zeri e scorrere a "sinistra" (nella memoria) tutte le celle successive. poi create una funzione che itera su tutto l'array in cerca di valori duplicati, che verranno "marcati" con degli 0. poi fate ripassare la funzione di eliminazione degli zeri.
è vero, questo algoritmo non è il massimo dell'efficienza (anzi è il male in persona), ma sicuramente è molto intuitivo, dunque di facile realizzazione.
è possibile utilizzare sia l'algoritmo iterativo che ricorsivo, dunque buona fortuna! ^^

cosa fanno queste routine?

1)
Codice:
.thumb
.align 2, 0xFF

.equ input, 0x2000000

main:

    Push {r0-r4, lr}
    
    ldr r1, =input
    ldr r0, [r1]
    
    bl func
    
    str r0, [r1]
    
    pop {r0-r4, pc}
    
func:
    push {r1-r4, lr}
    cmp r0, #1
    beq return
    cmp r0, #0
    beq return
    mov r1, r0
    sub r0, #1
    bl func
    mul r0, r1
    
return:
    pop {r1-r4, pc}



anche per stavolta mandatemi i compiti via MP, più avanti studierò un sistema per evitare che gli "esterni" "riciclino" i vostri compiti (perchè do per scontato che non vi copiate tra voi Wink Angel ).


crediti: gbatek, tonc



changelog e #todo

Se ti sono stato d'aiuto, clicca il tasto "mi piace" qua sotto! Grazie! : )

Il mio nuovo motto: don't ask for ETAs!

[Immagine: yKWdaxi.gif]

[Immagine: firma.png]
"L'uomo è ancora il più straordinario dei computer"


Guida di cui vado più fiero: Mega-Huge Bonnox' guide (to be expanded)


[Immagine: toothbrush_girl_by_timewyrm-d6i5ezm.jpg]

Ho un T.A.R.D.I.S. modello 40 ma non so usarlo. Pacman
ieri 04:59 PM
Cerca Cita
Rispondi 




Utente(i) che stanno guardando questa discussione: 1 Ospite(i)
Powered by MyBB, © 2002-2017 MyBB Group. Copyright © | 2015 Pokémon Hacking