MIPS Cross-Compiler einrichten

Ich hatte vor einigen Tagen einen Artikel über den MIPS Assembler geschrieben. Ich habe seit der letzten Woche dazu eine Hausaufgabe aufbekommen, in der steht, man solle den C Code in MIPS Assembler umschreiben, sodass das Ergebnis 1:1 wie Compilerproduziert aussieht.

Da ich dieses Anspruch für etwas utopisch halte, habe ich mir fix einen MIPS Cross-Compiler zugelegt, um die beiden Quelltexte zu vergleichen.

Installation des MIPS Cross-Compilers

Diesmal hatte ich nicht viel Lust mir die ganzen Pakete vom Source zu kompilieren, und habe auf die vorkompilierten Pakete zurückgegriffen. Diese können wir uns ganz einfach ins /opt Verzeichnis herunterladen und dort entpacken.

cd /opt
sudo mkdir mips-gcc
cd mips-gcc
sudo wget http://d2gosfejro2q5l.cloudfront.net/Mips_linux_toolchain_bin-1.1.tar.bz2
sudo tar xfvj Mips_linux_toolchain_bin-1.1.tar.bz2

Damit ist die Installation fast schon getan. Wenn wir den Cross-Compiler nutzen möchten, müssen wir noch die PATH-Variable anpassen. Das klappt einfach mit einem export:

export PATH="$PATH:/opt/mips-gcc/mips_linux_toolchain/bin"

MIPS Cross-Compiler nutzen

Wenn wir den von uns geschriebenen Assemblercode mit dem des Compilers vergleichen wollen, müssen wir erstmal den zu übersetzenden Code in C implementieren. Ein kleines Beispiel dafür wäre dieser Code:

int add(int a, int b);
int main(char **args) {
    (void) add(1,2);
}
int add(int a, int b) {
    return a+b;
}

Wenn wir diesen Quelltext abspeichern und danach den mips-linux-gnu-gcc aufrufen, haben wir die Binary, welche auf einem MIPS laufen würde.

mips-linux-gnu-gcc code.c -o code 

Die Binary decompilen

Möchten wir wieder an den Assemblercode gelangen, so nutzen wir einfach das Tool namens objdump:

mips-linux-gnu-objdump -d code > code.asm

Wenn man nun die .asm mit einem Editor öffnet, findet man darin unsere „add“ Funktion:

004005ac : <add>:
  4005ac:       27bdfff8        addiu   sp,sp,-8
  4005b0:       afbe0004        sw      s8,4(sp)
  4005b4:       03a0f021        move    s8,sp
  4005b8:       afc40008        sw      a0,8(s8)
  4005bc:       afc5000c        sw      a1,12(s8)
  4005c0:       8fc30008        lw      v1,8(s8)
  4005c4:       8fc2000c        lw      v0,12(s8)
  4005c8:       00621021        addu    v0,v1,v0
  4005cc:       03c0e821        move    sp,s8
  4005d0:       8fbe0004        lw      s8,4(sp)
  4005d4:       27bd0008        addiu   sp,sp,8
  4005d8:       03e00008        jr      ra
  4005dc:       00000000        nop

Wie wir sehen, macht der Compiler kräftigen Gebrauch vom Stack. Wir können hingegen ein viel kürzeres Programm schreiben. Folgende Zeilen beinhalten das komplette Programm inklusive der zweizeiligen Add (hier umbenannt, da sonst der Simulator meckert) Funktion:

.data
aa:  .word   1
bb:  .word   2

.text
main:
    lw $a0, aa  #Variable a ins $a0 laden
    lw $a1, bb  #Variable b ins $a1 laden
    jal ownadd     #Add aufrufen
    j exit      #Beenden

ownadd:
    add $v0, $a0, $a1   #a+b rechnen und ins $v0 ablegen
    jr $ra              #Zurückspringen

exit:
    li $v0, 10          #Exitcode
    syscall

Fazit

Ich denke die Forderung, einen Code 1:1 wie vom Compiler zu schreiben ist bzw. war übertrieben oder nicht so gemeint. Trotzdem ist es interessant zu sehen, welche Unterschiede zwischen dem Compiler generierten Code und dem selbst geschriebenen Code entstehen können.

~ Sebastian