L'ho fatto molte volte e continuo a farlo. In questo caso in cui il tuo obiettivo principale è leggere e non scrivere assemblatore, penso che questo si applichi.
Scrivi il tuo disassemblatore. Non allo scopo di creare il prossimo più grande disassemblatore, questo è rigorosamente per te. L'obiettivo è imparare il set di istruzioni. Sia che stia imparando l'assemblatore su una nuova piattaforma, ricordando l'assemblatore per una piattaforma che conoscevo una volta. Inizia con solo poche righe di codice, ad esempio aggiungendo registri e ping pong tra lo smontaggio dell'output binario e l'aggiunta di istruzioni sempre più complicate sul lato di input:
1) apprendere il set di istruzioni per il processore specifico
2) impara le sfumature di come scrivere il codice in assemblare per detto processore in modo da poter muovere ogni bit di codice operativo in ogni istruzione
3) impari il set di istruzioni meglio della maggior parte degli ingegneri che lo utilizzano per guadagnarsi da vivere
Nel tuo caso ci sono un paio di problemi, normalmente raccomando il set di istruzioni ARM per iniziare, ci sono più prodotti basati su ARM spediti oggi rispetto a qualsiasi altro (computer x86 inclusi). Ma la probabilità che tu stia usando ARM ora e non conosci abbastanza assemblatore per scrivere codice di avvio o altre routine sapendo che ARM può o non può aiutare quello che stai cercando di fare. La seconda e più importante ragione per ARM prima è perché le lunghezze delle istruzioni sono di dimensioni fisse e allineate. Disassemblare istruzioni di lunghezza variabile come x86 può essere un incubo come primo progetto, e l'obiettivo qui è imparare il set di istruzioni per non creare un progetto di ricerca. Il terzo ARM è un set di istruzioni ben fatto, i registri sono creati uguali e non hanno sfumature speciali individuali.
Quindi dovrai capire con quale processore vuoi iniziare. Suggerisco prima msp430 o ARM, poi ARM prima o secondo poi il caos di x86. Indipendentemente dalla piattaforma, qualsiasi piattaforma che valga la pena utilizzare ha schede tecniche o manuali di riferimento per programmatori gratuiti dal fornitore che includono il set di istruzioni e la codifica degli opcode (i bit e i byte del linguaggio macchina). Allo scopo di apprendere cosa fa il compilatore e come scrivere codice con cui il compilatore non deve lottare è bene conoscere alcuni set di istruzioni e vedere come lo stesso codice di alto livello viene implementato su ogni set di istruzioni con ogni compilatore ad ogni ottimizzazione ambientazione. Non vuoi entrare nell'ottimizzazione del tuo codice solo per scoprire di averlo reso migliore per un compilatore / piattaforma ma molto peggio per ogni altro.
Oh, per disassemblare i set di istruzioni di lunghezza variabile, invece di iniziare semplicemente dall'inizio e disassemblare ogni parola di quattro byte in modo lineare attraverso la memoria come faresti con ARM o ogni due byte come il msp430 (il msp430 ha istruzioni di lunghezza variabile ma puoi ancora cavartela andando linearmente attraverso la memoria se si inizia dai punti di ingresso dalla tabella dei vettori di interrupt). Per la lunghezza variabile si desidera trovare un punto di ingresso basato su una tabella vettoriale o sulla conoscenza di come il processore si avvia e segue il codice in ordine di esecuzione. È necessario decodificare completamente ciascuna istruzione per sapere quanti byte vengono utilizzati, quindi se l'istruzione non è un ramo incondizionato, si assume che il byte successivo a tale istruzione sia un'altra istruzione. È necessario memorizzare anche tutti i possibili indirizzi di ramo e assumere che siano gli indirizzi di byte iniziali per ulteriori istruzioni. L'unica volta che ho avuto successo ho fatto diversi passaggi attraverso il binario. A partire dal punto di ingresso ho contrassegnato quel byte come l'inizio di un'istruzione, quindi decodificato linearmente attraverso la memoria fino a raggiungere un ramo incondizionato. Tutti i target di diramazione sono stati contrassegnati come indirizzi iniziali di un'istruzione. Ho eseguito più passaggi attraverso il binario fino a quando non ho trovato nuovi target di ramo. Se in qualsiasi momento trovi un'istruzione di 3 byte ma per qualche motivo hai etichettato il secondo byte come l'inizio di un'istruzione, hai un problema. Se il codice è stato generato da un compilatore di alto livello, ciò non dovrebbe accadere a meno che il compilatore non stia facendo qualcosa di malvagio, se il codice ha un assemblatore scritto a mano (come ad esempio un vecchio gioco arcade) è possibile che ci saranno rami condizionali che non possono mai verificarsi come r0 = 0 seguito da un salto se non da zero. Potrebbe essere necessario modificare manualmente quelli dal binario per continuare. Per i tuoi obiettivi immediati che presumo saranno su x86, non penso che avrai un problema.
Raccomando gli strumenti gcc, mingw32 è un modo semplice per utilizzare gli strumenti gcc su Windows se x86 è il tuo obiettivo. In caso contrario, mingw32 plus msys è un'ottima piattaforma per generare un compilatore incrociato da sorgenti binutils e gcc (generalmente piuttosto semplice). mingw32 ha alcuni vantaggi rispetto a cygwin, come programmi significativamente più veloci e si evita l'inferno di cygwin dll. gcc e binutils ti permetteranno di scrivere in C o assembler e disassemblare il tuo codice e ci sono più pagine web di quante ne puoi leggere che ti mostrano come fare una o tutte e tre le cose. Se hai intenzione di farlo con un set di istruzioni di lunghezza variabile, ti consiglio vivamente di utilizzare un set di strumenti che includa un disassemblatore. Un disassemblatore di terze parti per x86, ad esempio, sarà una sfida da usare poiché non si sa mai veramente se è stato disassemblato correttamente. Alcuni di questi dipendono anche dal sistema operativo, l'obiettivo è quello di compilare i moduli in un formato binario che contenga istruzioni per contrassegnare le informazioni dai dati in modo che il disassemblatore possa eseguire un lavoro più accurato. La tua altra scelta per questo obiettivo primario è avere uno strumento che possa compilare direttamente in assembler per la tua ispezione, quindi sperare che quando compila in un formato binario crei le stesse istruzioni.
La risposta breve (ok leggermente PIÙ breve) alla tua domanda. Scrivi un disassemblatore per apprendere un set di istruzioni. Inizierei con qualcosa di RISCy e facile da imparare come ARM. Una volta che conosci un set di istruzioni, altri diventano molto più facili da raccogliere, spesso in poche ore, dal terzo set di istruzioni puoi iniziare a scrivere il codice quasi immediatamente utilizzando il foglio dati / manuale di riferimento per la sintassi. Tutti i processori che vale la pena utilizzare hanno una scheda tecnica o un manuale di riferimento che descrive le istruzioni fino ai bit e ai byte dei codici operativi. Impara un processore RISC come ARM e un CISC come x86 abbastanza per avere un'idea delle differenze, cose come dover passare attraverso i registri per tutto o essere in grado di eseguire operazioni direttamente sulla memoria con meno o nessun registro. Tre istruzioni di operandi contro due, ecc. Quando si ottimizza il codice di alto livello, compilare per più di un processore e confrontare l'output. La cosa più importante che imparerai è che non importa quanto sia buono il codice di alto livello scritto, la qualità del compilatore e le scelte di ottimizzazione fatte fanno un'enorme differenza nelle istruzioni effettive. Raccomando llvm e gcc (con binutils), né produconoottimo codice, ma sono multipiattaforma e multi target ed entrambi hanno ottimizzatori. Ed entrambi sono gratuiti e puoi creare facilmente compilatori incrociati da sorgenti per vari processori di destinazione.