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