Maschinencode in Turbo Pascal

  • Hallo zusammen,


    folgendes Basic-Listing, welches auf Maschinencode zurückgreift, würde ich gerne zu Turbo Pascal übertragen:



    Die DATA-Zeilen werde ich vermutlich in ARRAYS übertragen müssen, oder? Wie sieht es mit Pascal-Entsprechungen für die Basic-Befehle MEMORY, CALL, PEAK und POKE aus?


    Vielen Dank im voraus!

  • Wie sieht es mit Pascal-Entsprechungen für die Basic-Befehle MEMORY, CALL, PEAK und POKE aus?


    Kenn' jetzt Basic nicht so doll, MEMORY/PEEK/POKE dürften sich aber mittels des Mem-Arrays umsetzen lassen, für CALL gibt's keine Entsprechung, weil unnötig (eine Funktion/Procedur wird dort einfach mittels ihres Namens aufgerufen).

  • Für die Kombination aus "note" und "duration" würde ich Dir den Recotd Type ans Herz legen.
    Das PDF kennst Du ja sicher schon aus Deinem Thread über die Ports. 8-)
    Da sind die Records auf Seite 79 erklärt.
    In-line Machine Code wird ab 211 beschrieben.

  • Btw auf Seite 185 steht, wie am Sound erzeugt. Mit Sound(integer f_Hz), Delay(integer t_ms) und NoSound() um's wieder auszuschalten.

  • Die DATA Zeilen enthalten ja Unterschiedliches.
    Die ab der Zeile 540 sind das Maschinenprogramm für die Soundausgabe. Diese kannst Du direkt mit dem Kommando "INLINE" eintippen, da müssen dann aber natürlich die Checksummen (jeweils am Ende der Zeile weggelassen werden).
    siehe (Seite 274): http://fjkraan.home.xs4all.nl/comp/tp30/tp30_22.html


    Die DATA am Anfang sind die Tonhöhenwerte und noch was, was evtl. eine Standardlänge ist. Die würde ich als CONST Konstanten ablegen und zwar schön getrennt voneinander. CONST einfach deshalb, weil man die direkt mit Werten vorbelegen kann, was bei Variablen im Pascal zickig sein kann und je nach Compiler unterschiedlich ist.
    siehe (ganz unten): http://www2.informatik.uni-hal…cal/sprache/pas_arry.html


    Die tune$ Variable in der Mitte ist die Melodie in Noten. Am Besten vermutlich auch als Konstante so wie im BASICtext ablegen, oder wenn's richtig schick werden soll, als externes File einlesen, dann kann man da nämlich mit einem Texteditor prima dran rumbasteln und dann per Routine abspielen. In dem Fall per ASSIGN - RESET - READ - CLOSE (oder alternativ mit BLOCKREAD) in eine Variable einlesen.


    Hoffe die Links helfen bißchen weiter. Gruß.

  • Es freut mich, dass ich wieder so schnell einige Antworten und Hinweise bekommen habe, vielen Dank!
    Ja, ich werde mich mit dem Dokument und euren Hinweisen mal auseinandersetzen und ein bißchen rumprobieren.

  • So, nun habe ich mal versucht den Code in Turbo Pascal zu übertragen. Leider tritt schon bei der Compilierung in Zeile 53 ein Fehler auf (Fehler 53). Auch ansonsten ist mir noch einiges unklar: den Variablen t und c habe ich zwar Werte zugewiesen, aber diese muss ich noch irgendwie zur Sounderzeugung nutzen, oder sehe ich das falsch?



  • Hallo,


    ohne mich jetzt zu später Stunde zu tief in Dein Problem einarbeiten zu wollen, kann ich zumindest ein paar syntaktische Hinweise geben:


    Zeile 51: Mit var leitest du eine Variablendeklaration ein, ergo erwartet der Compiler eine Variable, statt dessen kommt begin. Also var weglassen.


    Zeile 82 und 95: Prozeduren rufst du einfach mit Namen auf, ohne das Wort procedure. Und den Ersatz für die Prozedur psound musst du natürlich auch an der entsprechenden Stellen aufrufen, also Zeile 87 statt 82. In Basic wird mit der Hilfsprozedur der Maschinecode in den Speicher "gepoked" - das ist hier nicht nötig. Der Maschinecode ist quasi automatisch in der Prozedur drin...


    Bei Deinen Variablendeklaration t und c wird's schwierig, weil die als Parameter an die Soundprozedur übergeben werden müssen. Höchstwahrscheinlich unterscheiden sich die Parameter-Übergabemechanismen von TP und BASIC aber, so dass das nicht ohne Anpassungen klappen wird.
    Weiterhin erwartet der Maschinecode, an einer speziellen Adresse zu liegen; auch das kann dir Probleme bereiten.


    Georg

  • Ich habe jetzt doch etwas genauer in den Maschinecode geschaut - anfangs werden wohl die Register gesichert, dann wird (wenn ich nicht irre, lange ist's her) u.a. eine Subroutine an Adresse 0xcb1b angesprungen, kurz danach die Register zurückgelesen und die gesamte Routine mit RET verlassen.


    Das ist schon so eine Sache, die nicht funktionieren kann, weil die Prozedur ja irgendwo liegt, aber nicht an Adresse 0xcb00. Ergo findet sich an Adresse 0xcb1b nicht die gewünschte Subroutine.


    Ich würde die gesamte Routine disassemblieren, die Sprungadressen bei Bedarf mithilfe des Adresszeigers (location content, Asterisk) berechnen und die Parameter t und c über ihre Namen referenzieren (was in TP ja recht elegant ging), wo sie im Original vermutlich über feste oder relative Adressen gefunden werden.


    Georg

  • Hallo Georg,


    vielen Dankfür Deine Unterstützung. Die formalen Fehler habe ich schnell behoben, das Programm lässt sich nun auch compilieren. Wird das Programm ausgeführt, bleibt es innerhalb der ersten Zählschleife hängen. Ich füge den aktuellen Stand mal als Anlage bei.


    Was jedoch Dein zweites Posting angeht, so muss ich zugeben, dass ich es nicht verstehe. Ich kenne mich leider weder mit Assembler noch mit dem Adresszeiger aus. :wacko:



    ChaosRom : Die Befehle aus Kapitel 19 des Reference Manual kann ich leider nicht nutzen, da ich mit CP/M plus arbeite. Der Sound-Befehl steht mir somit nicht zur Verfügung.



    Viele Grüße,
    Marcus

  • Was er meint ist eigentlich nicht schwer: Deine Soundroutine enthält ja als Maschinencode irgendwelche Prozessorbefehle, die hintereinander abgearbeitet werden. Wenn da aber eine Sprunganweisung dabei ist, dann steht da im Normalfall eine fixe Adresse an die der Prozessor dann "springt" und dort weitere Befehle abarbeitet. Das funktioniert auch prima, solange der Maschinencode an der richtigen Stelle im Speicher liegt - in Deinem Fall ab Adresse &CB00.
    Das müßtest Du also sicherstellen, damit der Code so funktioniert, wie er da abgedruckt ist.


    Deine Subroutine, die das Ablegen der Bytes in den Speicher übernimmt, hat aber keinerlei Anweisungen, wo man festlegt, wo der Code landen soll. Er wird also "irgendwo", nämlich da wo das Pascalprogramm gerade hin"compiliert" wird, im Speicher liegen. Nur mit einer völlig zu vernachlässigender Wahrscheinlichkeit wird er zufällig gerade bei &CB00 starten - und genau aus diesem Grund, springt dann eine Sprunganweisung, die z.B. nach &CB15 springt, in irgendwelchen fremden Code, oder in eine Wüste aus lauter &00 Bytes, oder irgendwas anderes - aber auf jeden Fall nicht dahin, wo die eigentlich Soundroutine weitergeht. Die Folge: Der Rechner friert ein, oder das Programm stürzt ab.


    Du hast zwei Möglichkeiten das zu umgehen:


    a.) Die Bytes definiert beginnend bei &CB00 ablegen, wenn das irgendwie per Compileroption oder anders möglich ist. Dazu muß aber vorher sichergestellt werden, daß da nicht was anderes schon "rumliegt". Der Vorteil ist: Die Routine kann so bleiben, wie sie ist, da die Adressen "passen"


    b.) Versuchen zu verstehen, was die Routine eigentlich macht. Dazu müßte man sie sinnvollerweise erstmal vom Maschinencode in Assembler "übersetzen", d.h. sie Dissassemblieren, damit sie vernünftig lesbar wird. Dann kann man schauen, ob Kommandos auftauchen, die an bestimmte Speicherstellen gebunden sind und nachfolgend versuchen diese Anweisungen so umzuschreiben, daß der Code an beliebiger Stelle im Speicher liegen kann.
    Wenn Du das mit dem Dissassemblieren machst und hier postest, könnte ich mir vorstellen, daß sich jemand findet, der was dazu weiß und Vorschläge hat. Den Byteblock dagegen wird sich vermutlich, so wie er ist, keiner angucken. (Wobei das auch geht, es gab/gibt da auf jeden Fall Leute, die sowas konnten/können und direkt aus dem Zahlenblock heraus sagen können, was das Programm macht - also Maschinensprache direkt lesen können. Das ist aber ziemlich selten.) (Was man sagen kann, ist, daß das Byte &CB ein paarmal auftaucht, weshalb da möglicherweise wirklich Sprünge innerhalb der Routine dabei sind; kann aber auch was anderes bedeuten.)

  • Code
    1. ~/_DOWNLOAD/PCWmusic$ hd ass
    2. 00000000 e5 d5 c5 dd e5 4e 23 46 eb 5e 23 56 69 60 cd 1b |.....N#F.^#Vi`..|
    3. 00000010 cb 3e 0c d3 f8 dd e1 c1 d1 e1 c9 f3 7d cb 3d cb |.>..........}.=.|
    4. 00000020 3d 2f e6 03 4f 06 00 dd 21 30 cb dd 09 3e 0b 00 |=/..O...!0...>..|
    5. 00000030 00 00 04 0c 0d 20 fd 0e 3f 05 20 f8 3c fe 0d 20 |..... ..?. .<.. |
    6. 00000040 02 3d 3d d3 f8 44 4f fe 0b 20 09 7a b3 28 09 79 |.==..DO.. .z.(.y|
    7. 00000050 4d 1b dd e9 4d 0c dd e9 fb c9 0d 0a 43 42 34 32 |M...M.......CB42|
    8. 00000060 20 6d 6f 64 75 6c 61 09 43 42 35 33 20 6c 70 32 | modula.CB53 lp2|
    9. 00000070 09 43 42 35 37 20 65 78 69 74 09 1a c9 74 09 1a |.CB57 exit...t..|
    10. 00000080 00 00 00 00 |....|



    Ich war mal so frei, das in Textform zu bringen, vielleicht erhöht das die Kommentarzahl ein wenig. Allerdings "vorlesen" müßte es jemand anders.


    Bei "call sub_cb1bh" steht die Verzweigung. Und die springt direkt an die Adresse &CB1B - und spätestens da schmiert Dir dann das Programm weg.
    Alles hinter &CB5A ist irgendwie Text und kein Programm mehr, sieht zumindest so aus; was auch immer das da macht.

  • Herzlichen Dank für die Mühe! Es wäre wirklich schön, wenn jemand eine Idee hätte, wie man den Mischinencode an die betreffende Stelle legen könnte, sofern das überhaupt in TP möglich ist. Wenn ich es richtig verstehe kann es natürlich sein, dass der betreffende Bereich bereits anderweitig z.B. durch TP selbst belegt wird. In dem Fall wird das vermeintlich kleine Problem dann doch etwas komplexer.

  • Hallo,


    Du hast eine ganze Reihe von Problemen. Zum einen die schon genannte Subroutine, die per fixer Adresse angesprungen wird (im Gegensatz zu den relativen Sprüngen JR, die nur den Abstand angeben, also von der echten Adresse unabhängig sind). Andererseits wird in IX ebenfalls eine feste Sprungadresse abgelegt (und noch modifiziert) - auch das musst Du anpassen. Und schließlich werden am Anfang mit der ganzen Bastelei um HL und DE die Parameter (vermutlich vom Stack) gelesen - das geht bei Deiner Programmierung auch nicht, weil Du globale Variablen verwendest.


    Ein Ablegen des Codes an die feste Adresse würde ich Dir nicht empfehlen - ich kenne jetzt die Speicherverwaltung Deines Rechners unter CP/M nicht, aber es ist eher zweifelhaft, dass das klappt. Wobei das Problem mit den Parametern immer bestehen bleibt.


    Das wird also schwierig ...


    Für mehr Hilfe fehlt mir gerade leider die Zeit :(


    Georg

  • So, jetzt kommen wieder ein paar weitere Details.
    Zunächst gebe ich Thoralf recht, alles hinter 0CB59H ist überflüssig. Bleibt das hier als Ausgangsbasis:



    • Die ersten 4 Zeilen des Codes sichern die Register auf den Stack
    • Die nächsten 10 Zeilen laden das Word, auf das HL zeigt, nach HL, und das Wort, auf das DE zeigt, nach DE. Das sind offenbar die beiden Parameter, die anscheinend doch nicht auf dem Stack liegen. Von der Art, wie Dein Basic den Aufruf call psound(t%, c%) umsetzt, habe ich keine Ahnung. Hier muss auf jeden Fall gearbeitet werden!
    • Anschließend wird die eigentliche Routine per Call mit absoluter Adresse angesprungen. Baustelle!
    • Danach wird noch ein 0Ch auf dem Port 0F8h ausgegeben, die Register restauriert und zurück ins Hauptprogramm gesprungen.
    • Die eigentliche Routine selber macht eine Menge Rechnerei und Schleifen mit den verschiedensten Lauflängen - im Wesentlichen unkritisch für Dich bis auf den Sprung JP (IX), der wieder mit absoluten Adressen arbeitet. Maßgeblich ist die Zeile LD IX,0CB30h - diese Adresse muss ebenfalls angepasst werden.
    • Die Ausgabe an den Port 0F8h wird wohl stimmen, wenn das ursprüngliche BASIC-Programm ebenfalls für Deinen Rechner bestimmt war.


    Jetzt ein paar Lösungsansätze (weitere Informationen auf Seite 274ff im schon genannten Manual):


    Auf Deine Variablen kannst Du in Inline-Befehlen relativ einfach zugreifen, etwa so:


    Code
    1. procedure Sound;
    2. begin
    3. inline($e5/ { PUSH HL }
    4. $d5/ { PUSH DE }
    5. $c5/ { PUSH BC }
    6. $dd/$e5/ { PUSH IX }
    7. $2a/C/ { LD HL,(Variable C) }
    8. $eb/ { EX DE,HL }
    9. $2a/T/ { LD HL,(Variable T) }
    10. .....


    Damit wäre der Inhalt der Variable C in DE und der Inhalt von T in HL. So ist es wohl gedacht - evtl. muss es umgekehrt sein, das hängt vom Mechansimus des BASIC ab.
    Dieses Konstrukt sollte mit etwas Glück den gesamten Part von 0CB05H bis 0CB0Dh ersetzen.


    Als nächstes ist die Kalkulation der Sprungadressen mit dem Location Counter dran, das sollte so gehen:


    Die Sprungweite von der Position, wo die Zieladresse steht bis zur Zieladresse selber ist im Originalcode 12 Byte (dezimal), von Adresse 0CB0Fh bis 0CB1Bh. Ergo müsste der Sprungbefehl so aussehen:


    Code
    1. ...
    2. inline(
    3. ...
    4. $cd/*+12/ { Call Subroutine }
    5. ...


    Analog die Berechnung für den Ladebefehl:


    Code
    1. ...
    2. $06/$00/ { LD B,0 }
    3. $dd/$21/*+7/ { LD IX,Sprungziel }
    4. $dd/$09/ { ADD IX,BC }
    5. ...


    Da ich den Verdacht habe, dass Du eine Inline-Routine nicht mit RET verlassen darfst (das will die selber machen), müsstest Du eigentlich anstatt des RET an der (alten) Adresse 0CB1Ah einen Sprung an das Ende der gesamten Routine machen. Alternativ sparst Du Dir die interne Subroutine und packst einfach alles, was vorher von 0CB1Bh bis 0CB58h stand (ohne den RET-Befehl) an die Stelle des Calls an Adresse 0CB0Eh. Das heißt, Du ersetzt den Aufruf der Routine durch die Routine selber. Dann lässt Du am Ende nach den POPs einfach den RET-Befehl ebenfalls weg, sparst Dir eine Adressrechnerei und alles müsste funktionieren. Die Sprünge innerhalb der Routine sind alle relativ, ebenso wie inzwischen auch der Ladebefehl. Das sollte alles keine Probleme machen.


    Inzwischen weiß ich übrigens auch, wie Du in TP Daten (und damit auch Maschinencode) an eine feste Adresse legen kannst. Du kannst eine Variable mit der Direktive absolute deklarieren und dabei die Adresse angeben, wo die Variable angelegt werden soll. Hier wäre es also möglich, z.B. einen array of byte der richtigen Länge an der Adresse 0CB00H anzulegen und die Variable dann mit diesen Codes zu füllen.
    Allerdings weiß ich im Moment keinen eleganten Weg dafür, der brutale geht so:


    Code
    1. var Maschinencode: array [0..x] of byte absolute $cb00;
    2. ...
    3. Maschinencode[0] := $e5;
    4. Maschinencode[1] := $d5;
    5. Maschinencode[2] := $c5;
    6. Maschinencode[3] := $dd;
    7. Maschinencode[4] := $e5;
    8. ..usw..


    Der Aufruf ist dann aber nicht mehr per Prozeduraufruf möglich, sondern nur noch über eine eigene Inline-Prozedur:


    Code
    1. procedure Sound;
    2. begin
    3. inline($cd/$cb00); { CALL 0CB00h }
    4. end;


    Das wäre also noch eine Möglichkeit - da Du aber den Zugriff auf Deine Variablen benötigst (was elegant nur innerhalb des Inlines geht), müsstest Du diesen Zugriff und damit auch die PUSHs und POPs mit in diese Sound-Prozedur packen; vom ursprünglichen Maschinencode bleibt dann nur noch die innere Routine, die Du dann auch direkt anspringen musst. Dazu wird dann natürlich die absolute Variable an die Adresse 0CB1Bh gelegt und nur noch mit dem Code der inneren Routine gefüllt.


    Ob das funktioniert hängt halt von Deiner Speicherverwaltung ab; wenn Du Pech hast, knallt das gewaltig. Auf jeden Fall vor jedem Probelauf Quelltexte sichern ;)



    Das ganze ist natürlich nur eine Folge von Hinweisen; ausarbeiten (und testen und auf die Nase fallen und Details rausfinden etc.) musst Du selber - vor allem, weil ich hier nur theoretisch dran gehen und nichts ausprobieren kann.


    Trotzdem viel Erfolg!


    Georg

  • Hallo Georg,


    vielen Dank für Deine Hilfe! Ich werde heute Abend und am Sonntag versuchen weiterzukommen...


    Viele Grüße,
    Marcus

  • :S Leider bekomme ich es nicht hin. Ich habe einiges ausprobiert, mal piept der Rechner und hängt sich auf, ein anderes Mal hängt er sich ohne Ton auf oder er steigt nur aus Turbo Pascal aus.


    Dennoch nochmals vielen Dank an alle, die versucht haben mir zu helfen! Auch wenn ich dieses Problem nicht gelöst habe, so habe ich dennoch einiges gelernt.

  • Wo liegt den das Problem? Im Maschinencode selber oder im Aufruf des selbigen?

    ;------------------------------------
    ;----- ENABLE NMI INTERRUPTS
    (aus: IBM BIOS Source Listing)