Assembler Tutorial

  • 1. Vorwort
    Willkommen zu meinen ARM7tdmi Thumb Assembler Tutorial.
    Erstmal möchte ich mich bei 2 Personen bedanken.
    prime, ohne dein Tutorial wäre es mir erst garnicht möglich gewesen,
    dieses Tutorial zu schreiben, von welchem ich mir erhoffe, dass es etwas
    ausführlicher wird.
    Sturmvogel, weil du mir fast immer geholfen hast wenn ich ein Problem
    hatte. Danke euch zweien.


    Zum Tutorial:
    Ihr solltet auf jedenfall als Basis mitbringen, Kentnisse darüber,
    wie der RAM des GameBoy Advance aufgebaut ist und was Bytes etc. Pointer und Addressen sind..


    2. Programme die ihr benötigt
    Ihr braucht den ARM-EABI Assembler, ihr findet ihn unten als Link
    Dazu solltet ihr im Besitz eines Hexeditors und einer Pokémon ROM sein.


    3. Unterschied ARM u. Thumb
    Es gibt bei dem ARM7tdmi von Advanced Risc Machines 2 verschiedene
    Arten von Anweißungen/Befehlen. Einmal ARM Befehle, welche 32-Bit groß sind, sprich 4 Bytes oder auch 1 DWORD. ARM Befehle können direkt von der CPU abgearbeitet werden. Dann gibt es noch Thumb Befehle, welche 16-Bit groß sind, bzw. 2 Bytes. Thumb kann nicht direkt abgearbeitet werden. Thumb Befehle müssen erst vom Prozessor in ARM Befehle umgwandelt werden. Thumb Befehle werden allerdings oft lieber als ARM Befehle benutzt, weil sie schneller sind und weniger
    RAM verbrauchen, da 1 Befehl nur 2 Bytes hat und nicht 4.
    So viel dazu.


    In diesem Tutorial werden wir uns nur mit dem Thumb Befehlssatz auseinandersetzen, da er am häufigsten im Bereich des Romhackings benötigt wird.


    4. Grundlagen


    Datengrößen


    BYTE: Zahl 0-255/8-Bits
    WORD: Zahl 0-65535/16-Bits/2 Bytes, wird im Hexeditor rückwärts gelesen, d.h aus FF AA wird das Word AAFF
    DWORD: Zahl 0-4294967295/32-Bits/4 Bytes, wird im Hexeditor rückwärts gelesen, aus 01 02 03 04 wird 04030201


    Register


    Bei jedem Prozessor gibt es sog. Register in die man mit Hilfe von Assembler verschiedene Werte schreiben kann, 2 Register addieren und das Ergebnis in ein
    anderes Register packen etc. Bei dem ARM7tdmi haben wir 17 Register, diese
    werden mit r0-r16 benannt und jedes Register ist 32bit lang.
    Im Thumb Modus können wir im Normalfall davon nur die Register r0-r7 und
    r13-r16 verwenden.


    In r13 ist der Stack Pointer gespeichert, d.h. die Addresse wo sich der Stack befindet.
    Der Stack ist so eine Art Zwischenspeicher auf den Werte und Inhalte von Registern gepackt werden können. Und der Stack funktioniert nach dem Stapelprinzip, d.h, dass alles, was auf den Stack gepackt wird, wieder in der umgekehrten Reihenfolge runtergenommen werden muss.


    r14 ist das sog. Link Register, d.h im Prinzip nichts anderes als, wenn
    wir in unserem Code in eine Unterfunktion springen, dass dort dann drin steht wohin die Unterfunktion nacher zurückspringen soll.


    In r15 steht der Code Pointer, d.h die Addresse von der Anweisung, die der Prozessor als nächstes ausführt.


    r16 ist das Status Register. Wir brauchen es im Normalfall nicht. Für die, die es interessiert Zitiere ich hier GBATEK.


    Da der "richtige" Status der Register für den Code sehr wichtig ist, damit er richtig funktioniert sollten wir, wenn wir eine Unterfunktion aufrufen, in dieser als erstes
    alle wichtigen Register auf den Stack packen und anschließend am Ende der Unterfunktion wieder zurückladen.


    Zum Abschluss des Grundlagen Teils bleibt gesagt, dass wir bei Assembler immer nur Daten
    ändern und hin und her schieben.


    5. Befehle
    So hier lernt ihr auch schon die ersten wichtigen Befehle ;)
    Ich sage mal so, ich werde hier nicht alle erklären, sondern die, die man am häufigsten braucht.


    Der MOV Befehl
    Mit MOV ist wie man sich vlt. denken kann MOVE (to move -> bewegen) gemeint.
    Wir können damit also Daten in ein Register kopieren. Das kann entweder eine fest angegebene Zahl von 0-255 sein oder der Inhalt eines anderen Registers.


    Beispiel:

    Code
    1. mov r0, #0x13
    2. mov r1, r0


    In der 1. Zeile schreiben wir in r0 den Wert 13 Hexadezimal.
    In der 2. Teile kopieren wir dann den Inhalt von r0 (also 13 Hexadezimal) und packen ihn in r1. Das war dann auch schon der 1. Befehl.


    Der ADD Befehl
    Der ADD Befehl ist zum addieren 2er Werte gedacht und hat folgende Syntax:
    add rX, rY, rZ
    und entspricht folgendem:
    rX = rY + rZ
    Dabei kann rZ entweder ein Register oder eine vorgegebene Zahl sein.


    Beispiel:

    Code
    1. mov r1, #0x5
    2. mov r2, #0x2
    3. add r0, r1, r2
    4. add r0, r0, #0x1


    Na, wer weiß es? Das Ergebnis ist hier natürlich, dass in r0, der Wert 8 steht.


    Der SUB Befehl
    Der SUB Befehl is t da zum Subtrahieren zweier Werte.
    Syntax:
    sub rX, rY, rZ
    rX = rY - rZ
    rZ kann wieder entweder eine vorgegebene Zahl oder ein Register sein.


    Beispiel:

    Code
    1. mov r0, #0x5
    2. mov r1, #0x2
    3. sub r2, r0, r1


    r2 = r0(5) - r1(2). Von Daher ist das Ergebnis unseres Codes also 3.


    Der MUL Befehl
    Der MUL Befehl ist zum multiplizieren zweier Werte gedacht.
    Syntax:
    mul rX, rY, rZ
    rX = rY * rZ
    rZ kann wieder einmal entweder eine Zahl oder ein Register sein.


    LDR, LDRB, LDRH Befehle
    Im Prinzip ist das ganze relativ einfach, es geht um Befehle zum Laden von Werten aus dem RAM oder ROM.
    Mit LDR lädt mein ein 32 Bit Wert von einer Addresse in ein Register.
    Mit LDRH lädr man ein 16 Bit Wert von einer Addresse in ein Register.
    Und mit LDRB lädt man ein BYTE von einer Addresse in ein Register.
    Die Syntax ist bei allen 3 gleich:
    ldr rX, addresse oder
    ldr rX, [rY] In den Fall liegt die Addresse in rY


    Um mal ein Beispiel zu geben (Mit dem @ Zeichen setzen wir ein Kommentar, dieser wird von dem Assembler dann ingnoriert.):


    Also im Prinzip laden wir nur 1 BYTE vom Offset 0x6000000 im Endeffekt.



    STR, STRH, STRB Befehle

    Jetzt schreiben wir in den RAM.
    Also mit diesen Befehlen könnt ihr den Wert aus einem bestimmten Register an eine bestimmte Addresse schreiben.
    Mit STR schreiben wir ein 32 Bit Wert, mit STRH ein 16 Bit Wert und mit STRB ein BYTE..
    Syntax (bei allen gleich):

    Code
    1. str rX, [rY]


    Dabei wird der Inhalt von rX ins Offset, welches in rY angegeben ist, geschrieben.


    Und ein Beispiel:


    Wir schreiben also im Endeffekt im RAM am Offset 0x6000000 den Wert 2.
    ACHTUNG: Typischer Anfängerfehler. In den Bereich 0x08000000-0x08FFFFFF kann nicht geschrieben werden, da der ROM Read Only ist.


    Der Befehl B
    Der Befehl B macht eigentlich nichts anderes, als an eine bestimme Codestelle zu springen.


    Syntax:
    b offset


    Beispiel:

    Code
    1. .align 2
    2. .thumb
    3. .global main
    4. main:
    5. b .unterfunktion @ Eine Unterfunktion ist nichts anderes als eine kleine Unterroutine in einer Funktion
    6. [bla...]
    7. .unterfunktion: @ Bei einer Unterfunktion
    8. @tuhe irgendwas..


    Der Befehl bx
    Der Befehl bx ist so ähnlich wie der Befehl b.
    Der einzige Unterschied liegt darin, dass bei bx nicht ein Offset,
    sondern ein Register angegeben, indem das Offset steht.
    Zu beachten ist, dass wenn der Code an der Zieladdresse Thumb ist (welches wir hier verwenden), sprich nicht ARM,
    dann muss das Offset +1 gerechnet werden.


    Vergleichen von Werten
    Wenn wir 2 Werte (sprich 2 register oder 1 register und 1 fester Wert) vergleichen, tuhen wir das erstmal
    in dem wir den CMP Befehl (von compare -> vergleichen) ausführen.
    Sprich also:

    Code
    1. cmp rX, rY


    bzw.

    Code
    1. cmp rX, #0xY


    Darauf folgt dann ein Conditional Jump. D.h., wenn bei dem Vergleich ein bestimmtes Ergebnis erzielt wurde, springt der Prozessor einer bestimmten Stelle im Code.
    Ich habe hier eine Liste für euch mit den wichtigsten Conditional Jumps.

    Code
    1. BEQ Werte sind gleich
    2. BNE Werte sind ungleich
    3. BGE Parameter 1 ist größer/gleich Parameter 2
    4. BLT Parameter 1 ist kleiner Parameter 2
    5. BGT Parameter 1 ist größer Parameter 2
    6. BLE Parameter 1 ist kleiner/gleich Parameter 2


    Beispiel:


    So jetzt könnt ihr raten welche unterfunktion ausgeführt wird !


    BIOS Funktionen.
    Das BIOS des GBAs bringt bereits einige handliche Funktionen mit sich, die es erleichtern,
    z.B. größere Datenmengen zu kopieren, oder LZ77 zu dekomprimieren.
    Jede Funktion hat eine Number. CpuFastSet z.B. 0xC.
    Man führt eine BIOS Funktion folgendermaßen aus:

    Code
    1. swi 0xNUMMER


    Da der Teil ein bisschen lang werden würde, würde ich hier jede einzelne Funktion behandeln, verweiße ich euch
    hier einfach mal auf GBATek:
    http://nocash.emubase.de/gbatek.htm#biosfunctions


    PUSH, POP
    So hier kommt jetzt der Stack ins Spiel
    PUSH macht im Prinzip nichts anderes als ausgewählte Register auf den Stack zu packen.


    Paar Beispiele:

    Code
    1. push {r0} @pushe r0
    2. push {r0-r3, r5} @ pushe r0 bis r3 und r5


    Und POP holt die Daten vom Stack wieder herunter in die ausgewählten Register.
    Es gibt natürlich sowas , wo man ein Register pusht und dann in ein anderes popt.
    Also z.B.:


    Code
    1. mov r0, #0x8
    2. push {r0} @ Wert von r0 auf den Stack packen
    3. pop {r1} @ Und Den Wert dann vom Stack nehmen und nach r1 packen


    Dann haben beide Register den Wert 8.


    6. Sichern von Registern
    Wie schon erwähnt, ist es wichtig, dass wenn man seinen eigenen Code irgendwo einbaut und ausführen lässt, dass wir in diesem Code die Register sichern und am Ende wiederherstellen. Das geht im Prinzip ganz einfach indem wir am Anfang unseres Code
    push {r0-r7, lr} schreiben und am Ende pop {r0-r7, pc}.
    Wir popen den Inhalt von lr am Ende nicht wieder in lr sondern in PC, also den Program Counter. Das machen wir, weil wir am Ende wieder zum ursprünglichen Code müssen und
    in lr steht ja die Addresse zu dem der Code zurückspringen sollen. Und das Zurückspringen machen wir in dem wir über den pop Befehl unteranderem den Inhalt vom LR in PC packen, wir ändern also den Program Counter und damit die Position des aktuell zu ausführenden Code auf das Offset wo der Code liegt, der unsere Funktion aufgerufen hat.


    Beispiel:

    Code
    1. .align 2
    2. .thumb
    3. .global main
    4. main:
    5. push {r0-r7, lr}
    6. @ tuhe etwas
    7. pop {r0-r7, pc}


    7. Ein Beispiel Code
    So nochmal ein Beispielcode der fast alles beinhaltet was ihr (hoffentlich)
    gelernt habt. Der Code ist für eine Feuerrot Deutsch ROM gedacht und ändert das
    Leben des 1. Pokemons im Team auf 1. Wie man vlt. sieht liegt das Leben des 1. Pokemons im Team an den RAM Offsets 0x02023c0c und 0x020242da ;)



    8. Compilieren bzw. Assemblieren
    OK das ist der weniger komplizierte Teil.
    Ihr geht in dem Assemler Ordner in den Unterordner 'cmp' und dann dort in
    den Ordner 'bin'.
    Dort erstellt ihr eine build.bat Datei die so aussieht:

    Code
    1. as -o output.o EureDatei.asm
    2. objcopy -O binary output.o Ausgabe.bin
    3. pause


    EureDatei.asm müsst ihr noch auf den Path zu eurem AssemblerSkript anpassen.
    Dann einfach Doppelklick auf build.bat und wenn er dann in der Konsole nichts mit 'Error: ' sagt, dann hat alles geklappt.
    Jetzt könnt ihr die Ausgabe.bin öffnen und den Code an einen freien Platz in der ROM kopieren.
    In einem XSE Script könnt ihr dann eure Routine mit callasm 0xOFFSET+1 aufrufen.


    9. Schlusswort
    So, erstmal danke fürs Lesen und Respekt, dass ihr solange ausgehalten habt.
    Ich hoffe euch hat das Tutorial geholfen, wie schon gesagt habe es sind nicht alle Befehle angesprochen worden, dass geht einfach nicht, wenn man man die Befehle ausführlich erklären will.


    Das Tutorial ist Copyright by hack!osa und darf ohne meine Erlaubnis nicht kopiert werden.


    In dem Sinne, mfG hack!osa


    ARM-EABI Assembler: Im Anhang

    Dateien

    • Assembler.zip

      (779,32 kB, 188 Mal heruntergeladen, zuletzt: )

    11 Mal editiert, zuletzt von hack!osa () aus folgendem Grund: Update 22.08.2012

  • Nu ja, mein Lieblingskapitel, leider muss ich sagen, wäre ich ein Anfänger in diesem Bereich würde ich persönlich warscheinlich nichts verstehen, bzw. selbst wenn ich es verstehen könnte wüsste ich nichts damit anzufangen weil ich nicht researchen könnte.
    Um einzelne Befehle kennen zu lernen empfinde ich das Tutorial nicht als schlehcht, allerdings sollte in einem Assembler Tutorial vorallem Dinge wie debugging, blablub vorkommen, damit man auch versteht was man macht. Meist läuft es ja so ab, dass man seinen Code einschläust in den Nintendo Code(Um bei Pokemon zu bleiben), der callasm Befehl ist nur die simpelste Methode eine ASM Routine aufzurufen.
    Letztlich erfüllst du das, was du durchgehen wolltest mit dem Tutorial hier, ich habe mir aber irgendwie mehr erwartet. Wenn ich zurück denke als ich kläglich versuchte Assembler zu lernen und letztlich nur durch fremde Hilfe es geschafft habe, weil driver mir mal irgendeinen Link gab, so würde ich das hier nicht verstehen.


    ~Sturmvogel


    Let the old ways live and prosper in the hearts of our young


  • In 1. Linie erfreut es mich, dass du der "Elite" beitreten möchtest und dich offensichtlich intensiv mit ASM beschäftigt hast.


    Allerdings, ich, als Anfänger bzw. ohne großartige ASM-Kenntnisse ausgestattet, verstehe das Tutorial bloß bedingt. Unbekannte Begriffe à la "DWORD" hättest du eventuell kurz erklären sollen, zur verwendeten Software hättest du ebenfalls 1 oder 2 Sätze schreiben können. Ressourcen, mit welchen man sich selbstständig Wissen bezüglich des RAMs etc. aneignen kann (Welches du ja vorraussetzt), wären ebenfalls eine Erleichterung.


    Konkret vermisse ich, wie Sturmvogel bereits änhlich fomulierte, einen praxisorientierten Bereich. Als Laie, der bislang keinen ASM-Script / Code bewusst zu Gesicht bekam, kann ich mit diesen Befehlen nichts anfangen. Einen kurzen ASM-Script, welcher im Spiel bereits integriert ist, auszuarbeiten und mit Hilfe dieses Scripts das Erlernte zu vertiefen bzw. praktisch anzuwenden (Vgl. diverse Scripting-Tutorials) wäre für mich persönlich eine große Hilfe.


    Keine Ahnung, ob meine Kritik überhaupt legitim ist oder hilfreich ist.
    Arbeite an diesem Tutorial, das Fundament passt ;).


    Lg.

  • gut, dass es endlich mal ein Tutorial auf Deutsch gibt. Auf Englisch habe ich ja auch schon welche gefunden, aber nur so zu 90% (also jetzt rein sprachlich) verstanden, was gemeint war. Ansonsten ein sehr, sehr schönes und gut verständliches Tutorial, was einigen Leuten den "Ersten Schritt" zum ASM-Scripten gut näher bringen kann. Auch gut strukturiert und gut dargestellt. Was man vielleicht besser machen kann ist dass du um die Scripte einen Spoiler oder so etwas macht. Also ich schließe mich da looper an, dass es schwer zu lesen ist.
    Also ich hab mich jetzt auch (auch im Schul-IT) etwas mit ASM beschäftigt. Aber ich habe trotzdem ein Frage. Und zwar gibt es ja verschiedene ASM "Arten" (nenn ich jetzt mal so vereinfacht). Dort sind die Befehle glaub auch zum Großteil gleich. Aber wo ist da ein Unterschied, bzw. Was ist der Unterschied?

    Der kostenlose Resourcen PKMN Markt!
    [Link aufgrund von Malware verdacht moderativ entfernt]
    Mit fast täglichem Update!
    Wer ein Teil davon werden will schickt mir eine E-Mail oder eine PN.


  • So danke erstmal fürs Feedback. Ich werde wohl das Tutorial noichmal etwas überarbeiten :P


    Alen : Joa kann ASM schon etwas länger (ca. 1 Jahr)
    Und btw ein DWORD sind einfach 4 rückwarts gelesen. D.h wenn du im Hexeditor siehst FA AD 03 0A dann ist das 0A03ADFA ;)


    Hackrex Danke für das doch sehr positive Feedback :P
    Die Idee mit den Spoilern finde ich nicht schlecht, werde ich umsetzen.
    Zu deiner Frage. Die ASM "Art" kommt auf den Prozessor drauf an. Jeder Prozessor hat seine eigene Art von ASM. Du hast recht oft heißen die Befehle gleich in Hex werden sie allerdings ganz anders geschrieben. Was noch anzumerken wäre, dass du eine bestimmte Art Assembler nur für eine Art Prozessor verwenden kannst.


    mfG hackiosa

  • O.k, ich habe alles fliesend verstanden :D


    Nur das wohl simpelste mislingt mir...


    Ich habe den Code als .asm gespeichert, nun macht er aber immer etwas falsches...?( (Glaube ich zumindest)


    Also ich habe diesen Code:



    Nun habe ich das als asm gespeichrt und in dern Ordner reingefügt. es erscheint aber immer dein Bespiel.txt. Klingt blöd aber das assemblen versteh ich nicht... :rolleyes: (Ich bin halt Gfe xD)


    Kann mir ma jemand helfen?


    Gfe

  • Naja wie schon erwähnt ist das hier lange nicht alles, wird dir aber bestimmt auch selbst noch bewusst werden. Assembler ist eine Sprache, ähnlich zu anderen Programmiersprachen, vielleicht ein wenig schwieriger, allerdings das was man damit anstellt, Daten kopieren, dafür sorgen, dass "das passiert was man will, dass es passiert", benötigt viel Verständnis der Logik der Hardware auf der man die Sprache verwedent.


    Anyway, ist ja nicht Thema des Tutorials, zum Thema Assembler wollte ich noch anmerken, ich verstehe nicht ganz das Problem, würde aber ebenfalls empfehlen die Dateiendungen zu überprüfen und vorallem beim verwenden des Programmes selbst darauf achten, dass man sich nicht verschreibt. Rechtschreibfehler frisst der Assembler ungern.


    ~Sturmvogel


    Let the old ways live and prosper in the hearts of our young


  • Sorry, dass ich dieses alte Thema wieder ausgrabe.
    Ich möchte Hack! dafür danken, dass er das geschrieben hat, ich fands sehr verständlich und hab innerhalb einer halben Stunde ziemlich durchgeblickt. Mir ergaben sich nur 2 Fragen.
    Wenn du bx lr verwendest, welches Register stellt lr da? Und braucht die Haupt bzw Unterroutine nicht eine Art "return" Befehl?


    Und einige Sachen weiterhin. Mit ASM schiebt man nur Datenmengen (Byte, Hword, Word) durch die Gegend, man braucht dazu jedoch Offsets, um damit etwas anfangen zu können. Wie researche ich die denn? Du solltest dein TUT vielleicht mit derartiger Thematik erweitern. Und die SWI-Befehle aus dem BIOS. Ich meine, ich weiß, denke ich, welchen ich für mein Vorhaben brauceh, aber was dort steht, als definiton, was z.B: in r0 stehen muss. Ich zitiere einmal.

    Ab "repeat below" hört es bei mir schon auf. Du solltest vielleicht doch mehr darauf noch eingehen.
    mfg Wodka ;)

    Wo war Gondor, als meine Klausurenphase begann?

    Einmal editiert, zuletzt von Wodka ()

  • Du hast hier die Beschreibung des LZ77-Kompressionsformats vorliegen. zuerst 32bit/4Bytes Header-Daten, danach den aufbau der komprimierten Daten, und dieser wieder natürlich immer wieder wiederholt (repeatet)


    Ansich musst du dich damit aber nicht auskennen, da es zum einfügen Tools gibt und zum Dekomprimieren gibt es swi-Funktionen. Diese sind auf GBA-Tek zu finden, wie auch eine grobe übersicht über die RAM, genaueres ist vom Spiel abhängig und muss geresearcht werden. Dieses Tut könnte dir dabei helfen: http://sfc.pokefans.net/3-suche-unbekanntes aber auch hier gibt es mehrere Tuts und mit der Zeit wirst du auch eigene "Researchstrategieren" entwickeln bzw diese oder schon zuvor erlernte effizienter zusammen fügen können.

    Users[4939].postCount++;


    • Weltherrschafts AG in Cooperation mit Weltuntergangs GmbH:
      "Wir sorgen dafür, dass sie sich keine Sorgen um Morgen machen müssen!"
  • Zu deiner ersten Frage, ich kenne "bx lr" nur in dem zusammenhang, dass es wie in XSE ein "return" ist, wenn du vorher ein "bl .Ort" hast.


    bl = call ; bx = return. Mithilfe von bl wird hat sozusagen in lr die aktuelle Stelle geschrieben (wie ein 2. push {lr}). Also so wie ich das bis jetzt verstanden habe, falls es nicht stimmt steinigt mich! :3
    (s. http://nocash.emubase.de/gbate…thumb19longbranchwithlink)


    Zu deiner zweiten Frage: Musst du glaub ich gar nicht verstehen. :P Also keine Ahnung, weil es ganz simpel ist, du gibst einfach das "Bild" Offset an.
    (s. https://board.romhackersworld.eu/board6-romhacking/board7-hacking-allgemein/6352-asm-by-prime-dialga)

  • Also ich bisher diesen Codeschnipsel mit Laz0r "gelernt"




    Damit schreibe ich dann (hoffentlich) meine Grafik ins Vram. Wie lasse ich die dann anzeigen?