ASM-Tut by prime

Wir sammeln alle Infos der Bonusepisode von Pokémon Karmesin und Purpur für euch!

Zu der Infoseite von „Die Mo-Mo-Manie“
  • Hier nun mein angekündigtes Tut zum ASM-Scripting:


    Inhalt:

    • Allgemeines zu ASM
    • Referenz
    • Register
    • Grundgerüst & -befehle
    • "leichte" Operatoren
    • "schwere" Operatoren
    • Credits



    Allgemeines zu ASM
    Also, in ASM ist die gesammte ROM und die RAM geschrieben. Es gibt zig Arten von ASM, aber für eine GBA-ROM interessiert uns nur das ASM für einen ARM7 Prozessor. Hier bei gibt es aber wieder 2 (mir bekannte) Arten: Das normale ASM und der Thumb-Befehlssatz.
    Und mit dem Thumb-Befehls beschäftigen wir uns in diesem Tut!


    Der Unterschried zwischen Thumb und normalem ASM ist der, das zwar normales ASM schneller, aber Thumb kleiner ist. ein ASM Opcode (also ein Befehl) ist compiliert 4 Bytes lang, wärend dessen ein Thumb Opcode nur 2 Bytes lang ist. Somit macht es durch aus Sinn in einer "kleinen" ROM lieber den platzsparenden Thumb-Befehlssatz zu nutzen.


    Wie unterscheidet jetzt aber der Prozessor zwischen Thumb und normalem ASM?
    Dies ligt am Pointer auf den Code: ein ASM Pointer hat immer den direkten Pointer, ein Thumb-Pointer das Offset + 1!
    Das wird immer gebraucht wenn man auf eine neues Thumb-Funktion pointet. Egal ob mit callasm in XSE oder mit dem bx Opcode. (Hier solltet ihr besonderst aufpassen... das ist bei mir Fehlerursache Nr. 1 XD)


    Natürlich kann auch hier der PC noch nicht viel mit dem Code anfangen, deshalb gibt es noch Compiler (die heißen hier Assembler wie die Sprache...) mit denen man die Befehle in Hex umsetzten kann. ich kann euch hier devkitpro und den GBAssebly von Sturmvogel empfehlen. Hier hätte der von Sturmvogel noch den Vorteil, dass er meine Referenzen integriert hat. Aber das ist dann euch überlassen^^


    Aber beim Devkitpro müsst ihr folgendes wissen:
    Zum Compilieren führt ihr im "bin" Ordner von devkitpro cmd.exe/command.exe aus.
    Dort gebt ihr zum compilieren/assemblieren

    Code
    arm-eabi-as -o {Pfad und Name mit Endung des Outputs (Endung ist in der Regel .o)} {Pfad und Name mit Endung des Inputs (Endung ist in der Regel .s oder .asm}

    ein.
    Um dann den entgültigen Code zu erhalten müsst ihr folgendes ausführen:

    Code
    arm-eabi-objcopy -O binary {Pfad und Name mit Endung des Outputs (Endung ist in der Regel .o)} {Pfad und Name mit Endung des Inputs (Endung ist in der Regel .s oder .asm}



    Referenz
    Ich beziehe mich von nun an auf die Referenz, das macht es mir etwas leichter, da ich darin schon die ein oder andere Funktion kurz erklärt und ähnliche zusammen gefasst hab.


    Jetzt müsst ihr davon noch nichts davon verstehen, ich erkläre alles noch^^!



    Register
    Ein Register ist so etwas wie eine vorgefertigte Variable, die ihr ohne definition (wie z.B. in C++: int c;) nutzen könnt.
    In ein Register kann höchstens ein word geschriebn werden.
    Jetzt fragen sich vermutlich wieder die meisten:
    Was ist ein word?


    In ASM/Thumb gibt es 4 wichtige größen:

    • Bit: sollte klar sein -> kann den Wert 0 oder 1 haben
    • Byte: sollte auch klar sein -> 8 Bites, die größte, mögliche Zahl ist hier (2^8)-1 = 255
    • hword (auch half word): ein hword besteht aus 2 Bytes oder auch 16 Bits ((2^8)^2)-1 = 65535
    • und schließlich das word: es ist 4 Bytes oder auch 32 Bits lang und hat damit den größten Speicherplatz: (((2^8)^2)^2)-1 = 4294967295


    Nun wieder zu den Registern:
    Wie am Anfang der Referenz beschrieben gibt es 16/17 Register, von denen können aber eigentlich nur die ersten 16 wirklich benutzt werden. Wobei für normale ASM oder Thumb Funktionen werden hier auch nur die ersten 8 gebraucht, also r0-r7. mit denen könnt ihr eigentlich alles machen was ihr wollt. Aber es gibt auch andere Register: r8, r9, r10, r11, r12, r13 = sp, r14 = lr, r15 = pc. leider weis ich hier nicht genau welchen sinn diese Register haben... ich kann nur soviel sagen: lr kann nur gepusht werden und pc kann nur gepopt werden. lr ist das Register in das mit der Jumpfunktion bl geschrieben wird, um später mit bx lr zurück zu jumpen (dazu mehr unten), und pc ist die leseposition. Wenn mal ein Pro gicht so läuft wie es soll, solltet ihr nachschauen, ob ihr lr nicht gepusht habt oder es doch gepusht habt oder ob ihr pc gepopt habt oder es nicht gepopt habt. Daran kann es schon mal liegen...


    Grundgerüst & -befehle
    Nach der ganzen Theorie nun mal die Praxis:
    Es gibt ein paar Dinge die indireckt einfluss auf euer Script nehmen:

    Code
    .align 2.thumb


    das sind schon mal die wichtigsten:
    damit beginnt ein jedes Thumb-Script.
    .align 2 könntet ihr dann später noch bei den richtigen Variablen gebrauchen...


    ok, nun gibt es noch push und pop, diese braucht, damit ihr nicht bei der Ausführung eures Scripts irgend welche wichtigen Werte überschriebt. push speichert und pop läd. Aber das sollte man sich mal in der Realität anschauen (@-Zeichen leiten Komentare ein, sie sind verleichbar mit z.B. /*Komentar*/):

    Code
    .align 2.thumb           @das ist mal der Start.xxx:              @hier beginnt euer Funktion, eigentlich könnte ihr euch das auch sparen, es sind dann etwas übersichticher... das xxx könnt ihr dann durch irgend etwas ersetzten)     push {r0, r1, r6, r7, lr}      @das würde die Register r0, r1, r6, r7 und lr speichern     push {r1-r6, lr}                   @das speichert r1, r2, r3, r4, r5, r6 und lr, natürlich braucht man in der Regel nur einen push, aber das soll es jetzt nur mal verdeutlichen! Ihr könnt natürlich die Kommasschriebweise und die mit dem Minus kombinieren     push {r0, r3-r5, lr}             @speichert r0, r3, r4, r5 und lr. pop ist das Gegenteil:     pop {r0, r1, r6, r7, pc}        @das würde die Register r0, r1, r6, r7 und lr laden. Aber das leuft genau so ab wie oben, folglich erkläre ich es jetzt nicht noch mal^^


    Was jetzt ganz wichtig ist, ich müsst in der umgekehrten Reihenfolge popen wie ihr gepusht habt:
    pusht ihr r0
    dann wieder ein neues push mit r1
    und nun holt ihr alles wieder mit einem pop r0
    und dann mit einem pop r1 zurück und wundert euch nun warum r0 nun den wert von r1 hat.
    Die Lösung: Pop in der umgekehrten Reihenfolge wie ihr gepusht habt!
    Das könnt ihr natürlich mit der im Codefenster beschriebenen furmulierung vereinfachen, aber hier könnt ihr es wieder so machen wie ihr es wollt!


    hier nun ein mögliches Grundgerüst:


    Was jetzt noch meiner Meinung nach dazu gehört ist das Laden von größeren Daten:
    Bis zu einem Byte könnt ihr mit mov verschieben:

    Code
    mov r0, r1   @der Wert aus r1 wird in r0 kopiertmov r0, #0x1   @1 wird in r0 kopiert


    bei großeren daten müsst ihr es mit Pointern oder relativen Pointern machen:

    Code
    ldr   @das ist der Ladebefehl,er läd immer ein word. ldrh läd ein hword und ldrb läd ein Byte, das wir aber nur bei Pointern verwendet...ldr r0, .offset     @das ist ein relativer Pointer, ... ldrb r1, [r0]        @... er läd einen normalen Pointer in r0 damit nun der Wert, in diesem fall ein Byte, von diesem Pointer ausgelesen werden kann und in r1 geschriebn werden kann. Ihr könnt natürlich auch ein hword laden, das wird dann als Variable genommen und ihr könn ihr einen Wert zu wiesen/auslesen..align 2.offset:.word 0x08800000


    Dann gibt es noch den Speicherbefehl str/strh/strb, sie gleich wie auch das Laden (str r1, [r0] @speichert den Wert aus r1 in das Offset oder die Variable in r0)



    "leichte" Operatoren
    Ok, damit habt ihr schon mal das Schwerste hinter euch!
    Ich habe dieses Kapitel "leichte" Operatoren genannt, da ich es am passendensten finde: wir beschäftigen uns hier mit den Operatoren


    add rA, rB, rC
    rB + rC = rA
    sub rA, rB, rC
    rB - rC = rA
    mul rA, rB, rC
    rB * rC = rA


    Sie funktionieren wie ganz normale Gleichungen: der 2. Wert wird mit dem 3. (Register oder Zahl, Zahlen werden bei Bytes mit einem # gekennzeichnet) addiert, subtrahiert oder multipliziert, danach wird das ergebnis in das Register an der ersten Stelle gestellt...
    Hier noch mal ein Beispiel:

    Code
    add r0, r2, #3  @addiert r2 mit 3 und schriebt das Ergebnis in r0mul r0, r0, r6  @multipliziert r0 mit r6 und schriebt das Ergebnis in r0, und so läuft es hier immer ab.



    "schwere" Operatoren
    Die Operartoren, die ich hier bespreche, hat vermutlich noch kaum jemand gesehen/mit ihnen gearbeitet, ausser er programmiert...
    Sie funktionieren vom Aufbau her gleich wie die "leichten" Operatoren...


    lsl, lsr und ror sind Operatoren, die die Position der Bytes im Register beeinflussen:
    wir nehmen für r0 einen Wert von (binär) 0011 an,
    lsl r0, r0, #0x1
    0011 left shift 1=0110
    alle Bits werden nach links geschoben, von rechts ist nun das erste Bit eine 0, wäre nun die Zahl größer als möglich fallen die ersten Zahlen von link weg, bis sie wieder eine mögliche Größe hat.
    lsr r0, r0, #0x1
    0011 left shift 1=0001
    alle Bits werden nach rechts geschoben, von links ist nun das erste Bit eine 0, die erste Zahl von rechts wäre nun eine Nachkommastelle, und fällt somit weg.
    ror r0, r0, #0x1
    0011 rotation 1=1001
    die erste Zahl von rechts wird gelöscht und links wird nun die selbe Zahl daran gehängt


    and r0, r0, #0x5
    0011 and 0101=0001
    der And-Operator gibt an, wie groß ein Bit maximal sein darf, ist es großer wird es zur maximalen Größe (da es aber binär ist werden alle Bits die mit 0 gekennzeichnet sind automatisch zu 0)
    orr r0, r0, #0x5
    0011 or 0101=0111
    der And-Operator gibt an, wie klein ein Bit minimal sein darf, ist es kleiner wird es zur minimalen Größe (da es aber binär ist werden alle Bits die mit 1 gekennzeichnet sind automatisch zu 1), das ist das Gegenteil von and, aber and und and lösen sich gegenseitig auf und or und or lösen sich gegenseitg auf.
    eor r0, r0, #0x5
    0011 xor 0101=0110
    vergleicht immer die Bits an der selben Stelle, sind sie gleich ist das Egrebnis an der Stelle 0, sind sie ungleich ist es 1
    mvn r0
    0011 mvn = 1100
    Dieser Operator dreht alle Bits in ihr gegenteil aus 0 wird 1 und aus 1 wird 0, mvn und mvn heben sich gegenseitig auf!


    So, und damit sind wir nun schon wieder fast am Ende! Die Jumps werden relativ ausführlich (finde ich) in der Referenz besprochen. Des halb hier nur das: alle Jumps sind in der Regel relative Jumps, nur bx wird mit Pointern verwendet.



    Credits
    Nun noch die Credits:
    Alles hier ist Copyright by prime aka prime-dialga.
    Kopieren ist strengstens verboten (auch einzelne Teile)!
    Verlinken ist Ok.