Assembler auf dem Raspberry pi

Das ist meine selbst geschriebene Fortsetzung von diesem Tutorial: lesson-1-hello-world
Der originale Link funktioniert nicht mehr aber hier ist eine Kopie noch gespeichert: lesson-1-hello-world
Und hier noch das fehlende Bild vom ersten Beispiel:
Beispiel1.png

Lösungen zu den Übungen vom Teil1:

  1. Versuchen Sie zu verstehen wie der Code funktioniert: da habe ich auch noch nicht alles verstanden.
  2. Da mein Compiler gar kein helloworld.o erzeugt hat erübrigt sich dieser Punkt.
  3. Meine Lösung: "gcc helloworld.s -o helloworld". Man kann es aber auch anders machen.
  4. Ich glaube ich hatte diese Frage falsch verstanden. Jedenfalls habe ich gemacht dass das Programm statt "Hello World!" jetzt "!dlroW olleH" ausgiebt.
  5. (**) Chinesischer Text (z.B. 世界你好!) Das ergab bei mir keinerlei Probleme.

Ausführliche Erklärungen zum Code aus Lektion 1:

Da ich noch nicht alles verstanden habe, ist dieser Abschnitt noch unvollständig.

Lektion 2

Makfile

Um zum Übersetzen der Beispiele nicht jedesmal soviel tippen zu müssen habe ich mir ein kleines Makefile geschrieben. Dieses muss im gleichen Ordner wie das Quellprogramm erstellt werden, also z.B. in whirlwind.
Der Name muss "Makefile" oder auch "makefile" sein, und dies ist der Inhalt:
CC=gcc
all: helloworld

helloworld: helloworld.s
       gcc helloworld.s -o helloworld
Dabei ist es wichtig dass die Einrückung mit einem Tabulator beginnt.
Eventuell reichen schon die erste zwei Zeilen im Makefile. Einfach mal ausprobieren.
Dann können wir es jeweils sehr einfach für neue Beispiele erweitern:
CC=gcc
all: helloworld Beispiel2 Beispiel3
Jetzt können wir, nachdem wir Änderungen in einem Beispielprogramm gemacht haben, ganz einfach mit "make" neu compilieren.
Auch muss der Editor nicht jedesmal verlassen werden, sondern wir speichern nach einer Änderung und tippen dann im Terminalfenster "make". Damit wir bei laufendem Editor weitere Befehle im Terminalfenster eingeben können, sollten wir den Editor so aufrufen:
pi@raspberrypi:/whirlwind$ emacs helloworld.s &
Wenn Sie als Editor nano verwenden geht das nicht. Aber Sie können dann einfach ein zweites LXTerminal starten um dort jeweils den make-Befehl einzugeben.

Machen Sie es sich bequem

Vielleicht haben Sie schon bemerkt, dass bei einigen Fehlermeldungen die Zeilennummer mit angegeben wird. Beim emacs können wir diese Information benutzen um jeweils direkt auf die entsprechende Zeile mit dem Fehler zu kommen. (Das geht vielleicht auch bei andern Editoren, aber dort weiss ich nicht wie das geht.)
Dazu müssen wir die Konfigurations-Datei des Emacs editieren. Diese Datei heisst ".emacs" und ist im Home-Verzeichnis zu finden. Dateien die mit einem Punkt beginnen sind beim Aufruf von ls normalerweise versteckt, um sie dennoch zu sehen müssen wir ls mit der Option "-a" aufrufen.
pi@raspberrypi: ~ $ ls -a
pi@raspberrypi: ~ $ emacs .emacs
Fügen Sie am Ende der Datei folgendes an:
(global-set-key "\C-b" 'compile)
(global-set-key "\C-n" 'next-error)
Dann speichern und emacs wieder verlassen. Gehen Sie jetzt wieder in whirlwind oder wie immer Sie den Ordner benannt haben. Fügen Sie nun im helloworld.s einen falschen Befehl ein (z.B. mv r0,r3). Gehen Sie danach mit dem Cursor auf irgendeine andere Zeile.
Um es jetzt neu zu compilieren drücken Sie gleichzeitig die Tasten Ctrl und b. Es erscheint dann eine Eingabezeile am unteren Rand mit voreingestelltem "make -k". Jetzt nur noch die Enter-Taste drücken um diesen Befehl abzuschicken. (man könnte hier auch einen andern Befehl eingeben z.B. "make beispiel2") Falls Sie noch nicht gespeichert haben, wird danach gefragt und wir antworten mit "y". Nun wird das Editor-Fenster auf zwei Teile geteilt, oben ist das Quellprogramm und unten stehen die Fehlermeldungen. Mit gleichzeitigem Drücken der Tasten Ctrl und n wird jetzt direkt auf den Fehler gesprungen.

Vergleich mit c-Programm

Wir wollen jetzt mal das Helloworld-Programm in c schreiben:
// hallo.c
#include <stdio.h>

int main()
{
 puts("Hallo Welt!");
 return 0;
}
Nach dem Anfügen von "hallo" im Makefile bei der "all:" Zeile können wir das dann mit "make" compilieren. Und ausprobieren mit "./hallo".
Um den Assembler-Code zu sehen, den der Compiler erzeugt hat geben wir folgende Befehle ein:
gcc -O1 -S hallo.c -o hallo.s
more hallo.s
Der Compiler hat also fast genau das erzeugt, was wir im Teil1 geschrieben haben. Wenn Sie die Optimierung "-O1" weglassen kommen noch ein paar überflüssige Befehle hinzu. Statt -O1 ist auch -O2 -O3 oder -Os möglich. Je höher die Nummer umso stärker wird optimiert. Bei -Os wird auf möglichst kurzen Code optimiert. In diesem einfachen Beispiel macht das alles aber keinen grossen Unterschied.

Schlussfolgerung aus diesem Abschnitt: offenbar haben wir im Teil1 nicht wirklich ein Assembler-Programm erstellt, sondern wir haben nur die Hauptfunktion eines c-Programms durch Assembler-Code ersetzt.

Assemblerprogramm und c-Programm mischen

Beim Assembler-Programmieren stösst man oft auf Dinge, die mit c ganz einfach sind, mit Assembler aber ziemlich kompliziert. Das Umgekehrte ist manchmal auch der Fall, Dinge die in Assembler einfacher zu machen sind als in c. Somit wäre es nett, wenn wir c und Assembler mischen könnten. Es gibt die Möglichkeit von sogenanntem Inline-Assembler. Dies ist aber ziemlich kompliziert und eignet sich deshalb nur für sehr kurzen Code.

Aus dem vorherigen Kapitel wissen wir, wie wir die Hauptfunktion eines c-Programms in Assembler schreiben. Nun können wir auch einfach statt "main" einen andern Namen verwenden um ein entsprechendes Unterprogramm in Assembler für ein c-Hauptprogramm zu schreiben.
So sieht dann das Assembler-Programm aus:

@ unterprog.s
@ Beispiel-Unterprogramm zum aufrufen vom Hauptprogramm
	.section	.rodata
	.align	2
.Lmessage:
	.string "Das ist das Unterprogramm."
	
	.section	.text
	.align 2
	.global	unterprog
	.type	unterprog, %function
unterprog:
	push	{lr}
	ldr	r0, =.Lmessage
	bl	puts
	mov	r0, #123	@ Rueckgabewert
	pop	{lr}
	bx	lr
Und im c-Programm rufen wir dann dieses Unterprogramm auf:
// hauptprog.c
#include <stdio.h>
int unterprog();

int main()
{
  int rueckgabewert;
  printf("Dies ist das Hauptprogramm.\n");
  rueckgabewert=unterprog();
  printf("Rueckgabewert vom Unterprogramm: %d\n",rueckgabewert);
  return 0;
}

Um dies zu compilieren erweitern wir noch unser Makefile:
CC=gcc -Wall -O3
L=gcc

all: helloworld hallo hauptprog

hauptprog: hauptprog.c unterprog.s
	$(CC) -S hauptprog.c -o hauptprog.s
	$(CC) -c hauptprog.c -o hauptprog.o
	$(CC) -c unterprog.s -o unterprog.o
	$L hauptprog.o unterprog.o -o hauptprog

clean:
	rm -f *.o *~
Dann compilieren und ausprobieren:
pi@raspberrypi:/whirlwind$ make
gcc -Wall -O3 -S hauptprog.c -o hauptprog.s
gcc -Wall -O3 -c hauptprog.c -o hauptprog.o
gcc -Wall -O3 -c unterprog.s -o unterprog.o
gcc hauptprog.o unterprog.o -o hauptprog
pi@raspberrypi:/whirlwind$ ./hauptprog
Dies ist das Hauptprogramm.
Das ist das Unterprogramm.
Rueckgabewert vom Unterprogramm: 123
pi@raspberrypi:/whirlwind$

... bei entsprechendem Interesse könnte hier bald eine Fortsetzung folgen ...

Kontakt-Formular

Hier das Kontakt-Formular für Fragen und Bemerkungen zum Tutorial.
Last update: 29.Dez.2012 / Rolf                                                                                 Validator