Autor Thema: Kleiner Assembler Thread  (Gelesen 8814 mal)

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« am: 13. August 2009, 08:40 »
Moin Moin,
da ich für diese Frage keinen passenden existierenden Thread gefunden habe, der halbwegs aktuell ist, erstelle ich mal wieder einen neuen.  :evil: Deswegen werde ich auch weitere Fragen dann hier stellen :)
 
Und zwar beschäftige ich mich einfach mal so etwas mehr mit Assembler, was heißt, ich habe meinen GRUB Assembler-Part, damit der Kernel überhaupt geladen wird, übernommen und bin dabei einen kleinen Kernel in reinem Assembler zu schreiben.
 
Das erste Hindernis hat aber leider nicht lange auf sich warten lassen.  :cry:
So sieht das momentan aus (klein und noch übersichtlich):
[SECTION .text]
global kernel_main
kernel_main:
push dword string ; parameter 1 der String
call video_write_string

ret

[SECTION .data]
string db "Hello World, From AsmOS",10,0

Wie man sieht, schiebe ich den String "string" als Parameter auf den Stack und rufe die Funktion auf. Hier versuche ich dann wie folgt den übergebenen String auszugeben:
video_write_string:
push ebp
;push edi
mov ebp, esp

mov esi, [ebp+8]
mov edi, 0xB8000
.printloop:
lodsb

test al, al
jz .printdone

stosb

mov al, 0x0F
stosb

jmp .printloop
.printdone:

mov esp, ebp
;pop edi
pop ebp
ret

Allerdings funktioniert dies nicht richtig. Der Ablauf ist, dass der string und danach einige kryptische Zeichen ausgegeben werden und dann anscheinend ein triple fault ausgelöst wird, da bochs neugestartet wird. Was habe ich hier falsch gemacht?  :?
 
Gruß Christian
« Letzte Änderung: 13. August 2009, 09:15 von ChristianF »

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 13. August 2009, 11:00 »
[SECTION .text]
global kernel_main
kernel_main:
push dword string ; parameter 1 der String
call video_write_string

ret
Das ist ret ist falsch. Wo soll das denn hinspringen? Außerdem holst du den Zeiger auf den string nicht vom Stack. Deswegem springt das ret zum String.
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #2 am: 13. August 2009, 11:17 »
Durch das ret soll er wieder in die Funktion zurückspringen, von wo sie aufgerufen wurde.
kernel_main wird von dem GRUB Assembler-Part aufgerufen:
extern kernel_main
kernel_startup:
push eax ; first Parameter - multiboot magic number
push ebx ; second Parameter - multiboot structure address
call kernel_main
;add esp, 8 ; remove previous pushed parameters
jmp $
Oder ist diese Art vorzugehen nicht sinnvoll? Sollte ich eventuell besser direkt dorthin (kernel_main) springen, oder einfach das ret weglassen?
 
Außerdem holst du den Zeiger auf den string nicht vom Stack. Deswegem springt das ret zum String...
Das war der Fehler, so was blödes. Was auf den Stack geschoben wird muss natürlich auch wieder runter...
 
Ich denke mal, dass man alle in der Funktion verwendeten Register erst sichern und am Schluss wiederherstellen sollte. Zumindest musste ich das in der Schule machen, als es um die Microcontoller-Programmierung ging.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #3 am: 13. August 2009, 11:31 »
Oder ist diese Art vorzugehen nicht sinnvoll?
Doch das ist in Ordnung. Ich dachte das sollte vielleicht zu GRUB zurückspringen oder so. Oder dass du dir darüber überhaupt gar keine Gedanken gemacht hast. Aber so passt das schon.
 
Ich denke mal, dass man alle in der Funktion verwendeten Register erst sichern und am Schluss wiederherstellen sollte. Zumindest musste ich das in der Schule machen, als es um die Microcontoller-Programmierung ging.
Naja, ich denke mal in der Schule solltest du halt nach Schema F programmieren, damit du (oder dein Lehrer) nicht dauernd Fehler suchen musst, die wegen veränderter Register auftreten.

Aber ich finde Assemblerprogrammieren mit Aufrufkonventionen (dann noch die aus C -.-), Register sichern, Parameter auf dem Stack, etc langweilig ... da kannste gleich bei C bleiben. Da darfste dir auch ein paar Makros machen, und mit 8 globalen Variablen, die wie die Register benannt sind, arbeiten, wenn du willst, dass der Code aussieht wie Assembler. Ich glaube ich bau da mal ein Framework ;)
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #4 am: 13. August 2009, 12:29 »
Oder ist diese Art vorzugehen nicht sinnvoll?
Doch das ist in Ordnung. Ich dachte das sollte vielleicht zu GRUB zurückspringen oder so. Oder dass du dir darüber überhaupt gar keine Gedanken gemacht hast. Aber so passt das schon.
 
Ich denke mal, dass man alle in der Funktion verwendeten Register erst sichern und am Schluss wiederherstellen sollte. Zumindest musste ich das in der Schule machen, als es um die Microcontoller-Programmierung ging.
Naja, ich denke mal in der Schule solltest du halt nach Schema F programmieren, damit du (oder dein Lehrer) nicht dauernd Fehler suchen musst, die wegen veränderter Register auftreten.

Aber ich finde Assemblerprogrammieren mit Aufrufkonventionen (dann noch die aus C -.-), Register sichern, Parameter auf dem Stack, etc langweilig ... da kannste gleich bei C bleiben. Da darfste dir auch ein paar Makros machen, und mit 8 globalen Variablen, die wie die Register benannt sind, arbeiten, wenn du willst, dass der Code aussieht wie Assembler. Ich glaube ich bau da mal ein Framework ;)
Ich bin ja noch am experimentieren.  :roll:
 
Wenn ich meine Funktion wie hier aufrufe mit "push word ..." muss ich ja am Ende esp immer um 4 (Rücksprungadresse) und die Anzahl der gepushten Parameter multipliziert mit 4 erhöhen, also hier dann das "add esp, 8". Oder habe ich da was falsch verstanden?

Cjreek

  • Beiträge: 104
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 13. August 2009, 12:47 »
Hi,

Also wenn du im 32-Bit Modus bist und alle deine Parameter über den Stack übergibst, dann musst du am Ende deiner Funktion folgendes schreiben

ret ParamCount*4 ; <-- 4 Byte(32 Bit) pro Parameter
also als Bsp:

push 20
push 50
call addieren
jmp $

addieren:
     push ebp
     mov ebp, esp
     
     mov eax, [ebp+0x08]
     mov edx, [ebp+0x10] 

     add eax, edx

     pop ebp
     ret 8   ; = ret + add esp, 8

Lg
Cjreek
« Letzte Änderung: 13. August 2009, 12:49 von Cjreek »
"Programmers talk about software development on weekends, vacations, and over meals not because they lack imagination,
but because their imagination reveals worlds that others cannot see."

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #6 am: 13. August 2009, 13:04 »
Gut zu wissen.  :-D
 
Und wieder eine weitere Frage...
Muss ich in jeder Assembler-Datei die Sektionen angeben?
Quasi so:
[SECTION .text]
global func
func:
    ; mach wass
    ret
[SECTION .data]
str db "Bla Fasel",0
Oder könnte man das auch weglassen?

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 13. August 2009, 14:06 »
Wenn ich meine Funktion wie hier aufrufe mit "push dword ..." muss ich ja am Ende esp immer um 4 (Rücksprungadresse) und die Anzahl der gepushten Parameter multipliziert mit 4 erhöhen, also hier dann das "add esp, 8". Oder habe ich da was falsch verstanden?
Nein, die Rücksprungadresse wird schon vom ret vom Stack genommen.
Du musst dich nur um die Parameter kümmern die du vorher gepusht hast.

bei einem
ret xmusst du aufpassen, denn nach der C-Aufrufkonventionen kümmert sich der Aufrufer um den Stack. Du kannst das also nicht verwenden, wenn du die Funktion von C aus aufrufen willst.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 13. August 2009, 14:11 »
Edit: zu langsam -.-

Dann beantworte ich halt die nächste Frage:

Und wieder eine weitere Frage...
Muss ich in jeder Assembler-Datei die Sektionen angeben?
Quasi so:
[SECTION .text]
global func
func:
    ; mach wass
    ret
[SECTION .data]
str db "Bla Fasel",0
Oder könnte man das auch weglassen?
Nein, kannst du im Allgemeinen nicht weglassen. (Ausnahme wäre .text, weil das Standard ist.)
« Letzte Änderung: 13. August 2009, 14:13 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #9 am: 13. August 2009, 14:30 »
Gut gut.
Die sache mit den Aufruf-Konventionen muss ich mir mal genauer anschauen. Da ich allerdings nur in Assembler programmieren möchte, denke ich, dass ich das "ret x", also nicht die __cdecl-Konvention, nehmen kann.
Ich müsste allerdings erst nochmals schauen und genau abwägen, was ich letzten endes nutze.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #10 am: 14. August 2009, 11:34 »
So...
ich habe mal etwas weiter experimentiert und eine Funktion zustande gebracht, die den Bildschirm löscht. Diese funktioniert auch, da ich aber nicht so gut in Assembler bin, wollte ich einfach mal fragen, was überflüssig sein könnte, oder was ihr anders gemacht hättet.
 
Hier der Code:
video_clear_screen:
push ebp
mov ebp, esp

; Register sichern
push edi
push ebx
push eax

; Adresse aus Variable holen (0xB8000)
mov edi, [video_address]
; Prüfwert berechnen
mov eax, 25
mov edx, 80
mul edx
mov FUNCTION_LOCALVAR_1, eax

mov ebx, 0
.loop_cls:
; Leerzeichen ausgeben, mit schwarzem
; hintergrund und weißer schrift
mov al, 0x20 ; blank
stosb
mov al, 0x0f ; Black background, white text
stosb

; Schleifen-Variable um 1 erhöhen
inc ebx
; Wurde das Ende erreicht?
cmp ebx, FUNCTION_LOCALVAR_1
; Nein? -> Wiederholen :D
jne .loop_cls

; Register wiederherstellen
pop eax
pop ebx
pop edi

mov esp, ebp
pop ebp
ret

 
Bei "FUNCTION_LOCALVAR_1" handelt es sich um ein Makro, das wie folgt aussieht: "%define FUNCTION_LOCALVAR_1 [ebp-4]".
Wenn ich nun eine solche Variable auf dem Stack benutze, muss ich die dann auch vorher reservieren und später wieder freigeben, oder kann ich das machen, wie es mir beliebt?

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #11 am: 14. August 2009, 12:02 »
Hi,

du musst für lokale Variablen auf dem Stack den Speicher reservieren, sonst werden diese beim nächsten Interrupt oder Funktionsaufruf überschrieben.

Bei dir ist an [ebp - 4] übrigens der gesicherte Wert von edi. Ich weiß nicht, ob du den überschreiben willst.

Ich hätte einfach sowas gemacht:
; Leert den Bildschirm
; Diese Funktion verändert eax, ecx und edi. <-- sonstige Seiteneffekte
video_clear_screen:
    mov ecx, 80*25
    mov ax, 0x0f20
    mov edi, 0xb8000
    rep stosw
    ret

(ungetestet)
« Letzte Änderung: 14. August 2009, 12:05 von PorkChicken »
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #12 am: 14. August 2009, 12:32 »
Der sollte natürlich nicht überschrieben werden...
 
Wenn ich deine Funktion richtig verstanden habe, ist rep zu vergleichen mit einer For-Schleife, die von 0 bis 80*25 hochzählt und jedes mal "stosw" ausführt.
Getestet wurde es eben von mir und es hat funktioniert...  :-)

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #13 am: 14. August 2009, 12:59 »
Wenn ich deine Funktion richtig verstanden habe, ist rep zu vergleichen mit einer For-Schleife, die von 0 bis 80*25 hochzählt und jedes mal "stosw" ausführt.
Genauer gesagt zählt sie (r/e)cx bis 0 runter.

wenn du dennoch mal Locale variablen brauchst reservierst du dir den Speicher mit
push ebp
mov ebp, esp
sup esp, <x-Bytes>
oder mit dem Äquivalent dazu 'enter <x-Bytes>, 0', was aber aus mir unbekannten Gründen so eigentlich nie verwendet wird, anders als
'leave', dem Äquivalent zu 'mov esp, ebp; pop ebp'.


@PorkChicken: Wenn schon ohne Calling Convention, dann fehlt in deiner Funktionsbeschreibung:
; setzt DF = 0 vorraus

PS: ChristianF, du setzt hoffentlich nicht wegen der "Geschwindigkeitsvorteile" auf asm.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #14 am: 14. August 2009, 13:15 »
Nein, ich möchte keine Geschwindigkeitsvorteile erzielen. :)
Ich möchte einfach nur etwas mehr Assembler programmieren, anstatt die ganze Zeit über C. :roll:
 
Wegen des "setzt DF = 0 vorraus", heißt das, ich muss vor jedem Aufruf der Funktion prüfen, ob df in FLAGS ungleich 0 ist und es dann auf 0 setzen? Das finde ich an und für sich nicht schön, es sei denn, das "Direction Flag" kann nur von Hand gesetzt werden...

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #15 am: 14. August 2009, 13:33 »
Wegen des "setzt DF = 0 vorraus", heißt das, ich muss vor jedem Aufruf der Funktion prüfen, ob df in FLAGS ungleich 0 ist und es dann auf 0 setzen? Das finde ich an und für sich nicht schön, es sei denn, das "Direction Flag" kann nur von Hand gesetzt werden...
Nein, nein… das Flag lässt sich nur von Hand ändern, entweder mit STD bzw. CLD oder über das FLAG-Register. Aber sobald du irgend wo ein STD verwendest wird dein Bildschirm plötzlich nicht mehr sauber.

Deshalb finde ich Konventionen, dir z.B. sagen das DF vor dem Start, und nach dem ende einer Funktion 0 sein muss, gar nicht schlecht.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
« Antwort #16 am: 14. August 2009, 14:04 »
Normalerweise muss man auch nicht am Direction Flag rumfummeln. Auf vorwärts stellen und gut is ^^
Dieser Text wird unter jedem Beitrag angezeigt.

ChristianF

  • Beiträge: 296
    • Profil anzeigen
    • DeutschOS - Betriebssystem Projekt
Gespeichert
« Antwort #17 am: 14. August 2009, 14:55 »
Gut...
 
Wie das eben so ist, habe ich noch eine kleine Frage... :D
Folgendes Konstrukt funktioniert einwandfrei:
push byte 0x25
call video_write_char

Nun durchlaufe ich allerdings in der Funktion zum Ausgeben eines Strings diesen und lade ihn mit lodsb nach al. Folglich habe ich mir folgendes gedacht:
push byte al
call video_write_char
Allerdings funktioniert dies nicht, da mir der Fehler "error: invalid combination of opcode and operands" entgegengeworfen wird. Also habe ich ax und auch mal eax auf den Stack geschoben und dann die Funktion aufgerufen:
push ax ; oder auch 'push eax'
call video_write_char
Allerdings wird so immer nur der letzte Buchstabe ausgegeben. Was mache ich da Falsch? Wenn in "al" ein Leerzeichen ist, wäre es das gleiche, wie folgender Aufruf:
push byte 0x20
call video_write_char

Wieso funktioniert das nicht, bzw. nur teilweise, wenn ich ax oder eax auf den Stack schiebe?

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
« Antwort #18 am: 14. August 2009, 15:15 »
Mit den zwei Zeilen kann ich dir nicht sagen wo dein Fehler ist.

Funktioniert denn mit:
push byte 0x25
call video_write_char
Die Ausgabe mit eines '%', viel mehr sollte sich nicht tun?

Und das tut, dann auch noch mit push eax, (bei eax = 0x25); daran liegt der Fehler nicht.
« Letzte Änderung: 14. August 2009, 15:18 von MNemo »
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

Cjreek

  • Beiträge: 104
    • Profil anzeigen
Gespeichert
« Antwort #19 am: 14. August 2009, 15:26 »
Hi,

Ich habe das Gefühl dass du alle Zeichen auf die gleiche Stelle schreibst.

Zeig mal deine Funktion um Strings auszugeben. Ich glaube du schreibst alle Chars "übereinander" wodurch nur der letzte sichtbar ist.

Lg
Cjreek
"Programmers talk about software development on weekends, vacations, and over meals not because they lack imagination,
but because their imagination reveals worlds that others cannot see."

 

Einloggen