wird bei der Berechnung der Sprungadresse $+4 für $ die Adresse von jz
genommen oder die nachfolgende?
Ich habe im Datenblatt nachgeschaut und sehe, dass sowohl Opcodes für
"jz rel" wie auch für "dec direct_byte" 2 Bytes beanspruchen. Also 4
zusammen.
Wenn nun $+4 auf die Adresse von "mov" zeigt, dann ist mir der Code
klar.
Aber ich finde die Berechnungsweise von $+x nirgendwo. Sicher weiß
jemand aus dem Forum das und kann das bestätigen oder Link zur
Beschreibung geben :)
Gruß und Danke
Üblicherweise beziehen sich solche Angaben in Assembler auf die Adresse
des Befehls selbst, nicht auf die möglicherweise anders arbeitende
Berechnungsweise des Befehls.
das kannst du auch selbst probieren:
Label: sjmp label ;und
sjmp $
erzeugen den gleichen code. bedeutet das $ ist ein Platzhalter (label)
auf den Anfang des Befehls. $+4 zeigt 4 Bytes weiter.
Ich würde nur in Ausnahmen mit dem $ Platzhalter arbeiten. Besser ist es
Label zu verwenden. Mit dem $ kann es schnell Unfälle geben wenn man am
Code was ändert.
https://www.keil.com/support/man/docs/a51/a51_wp_locationcounter.htm
(prx) A. K. schrieb:> Üblicherweise beziehen sich solche Angaben in Assembler auf die Adresse> des Befehls selbst, nicht auf die möglicherweise anders arbeitende> Berechnungsweise des Befehls.
War es vielleicht beim 6502? Wenn ich mich richtig erinnere, gab es auch
CPUs, die den Offset zum nächsten Befehl verwendet haben, da bei
Ausführung des rel. Sprunges der PC schon auf den nächsten Befehl
zeigte.
Oder H8, H8S, H8SX? Zu lange her.
Kann der Keilassembler denn keine lokalen Sprungmarken,
dass man mit dem "$" herummurksen muss?
Frueher oder spaeter faellt man mit dem naemlich doch
mal auf die Nase...
Cartman schrieb:> Kann der Keilassembler denn keine lokalen Sprungmarken,> dass man mit dem "$" herummurksen muss?
nein lokale Labels kenne ich nur von Assembler Dialekten die sowas wie
PROC/ENDP unterstützen also zB Turbo Assembler auf dem PC. Da kann man
dann Labels generieren die nur innerhalb der Proc gültig sind.
Beim Keil A51 sind Labels auf Dateiebene global. Er kennt allerdings
lokale Labels in Macros.
m.n. schrieb:> War es vielleicht beim 6502? Wenn ich mich richtig erinnere, gab es auch> CPUs, die den Offset zum nächsten Befehl verwendet haben, da bei> Ausführung des rel. Sprunges der PC schon auf den nächsten Befehl> zeigte.
Das ist häufig so, hat aber keinen Einfluss auf die Arbeitsweise eines
symbolischen Assemblers. Der berechnet erst $+4 als Zieladresse,
basierend auf der Befehlsadresse, denn das ist zu diesem Zeitpunkt die
einzige, die er kennt. Bei der Codeerzeugung subtrahiert er dann jene
Adresse, auf die sich der Maschinenbefehl bezieht.
Cartman schrieb:> Kann der Keilassembler denn keine lokalen Sprungmarken,> dass man mit dem "$" herummurksen muss?
Kurze Suche im Web ergab nur welche bei Keil für ARM.
Spaetestens wenn der Assembler noch Befehle einfuegt
oder die Reihenfolge der Befehle aendert, ist beim
"$" Schluss mit lustig.
Beispiele:
TMS320 Assembler nimmt Aenderungen vor, um Silicon Bugs
zu umgehen.
MIPS Assembler aendert je nach eingestelltem Modus die
Reihenfolge der Befehle um Zyklen zu sparen.
XC8 PIC Assembler "streut" Befehle ein, um das
Bankregister/bits zu setzen.
Beim 8052 koennte ich mir erweiterte Architekturen
vorstellen, die manche Befehle mit "Praefixen" kennzeichnen
muessen, die dann so auch nicht im Assemblerquelltext
stehen.
Wie man sieht, betritt man mit dem "$" eher ein Minenfeld...
Schon der blosse Wechsel des Assemblers kann unerwartete
Ergebnisse hervorbringen.
Mikroschnarpel schrieb:> Wie man sieht, betritt man mit dem "$" eher ein Minenfeld...
Ist ein Guter Rat :-)
Obwohl die Meisten Assembler die ich kenne und das sind nicht wenige,
interpretieren das $ als die Adresse in welcher der Befehl steht.
Beispiel im MSP430 Assembler:
1
BR $ ; Neverending loop
wird gerne in den Applikation Examples von TI gebraucht um den Prozessor
"Festzufahren",um beispielsweise den Watch Dog zu prüfen, oder bei
kleinen Applikation Examples auf ein Interrupt zu warten ohne den
Prozessor schlafen zu legen.
Man kann $ Perimeter auch brauchen um etwa ein Link auf den Befehl zu
speichern ist aber echt nicht zu empfehlen sobald man "$+x" verwendet.
Auch der IAR kann im Assembler Befehle einfügen um Siliconbugs zu
umgehen!
Dann fällt man so richtig schön auf die Nase und sucht eine halbe
Ewigkeit den Fehler ;-)
73 55
m.n. schrieb:> War es vielleicht beim 6502? Wenn ich mich richtig erinnere, gab es auch> CPUs, die den Offset zum nächsten Befehl verwendet haben, da bei> Ausführung des rel. Sprunges der PC schon auf den nächsten Befehl> zeigte.> Oder H8, H8S, H8SX? Zu lange her.
Bei der Z80 ist die relative Endlosschleife 18 FE (JR -2), d.h. der PC
zeigt bei der Ausführung schon auf den nächsten Befehl.
Bevor man heute herummeckert, muß man beachten, daß der Keil C-Compiler
aus dem Anfang der 80er stammt. In dessen Listings ist dann auch die
Form $+4H zu finden und auch nur für diese kurzen Sprünge. Weitergehende
Verzweigungen oder auch das Überspringen von INC wurden alle mit Labels
in der Form ?Cxxxx versehen.
Seinerzeit hatten Rechner wenig Speicher, die Editoren waren rudimentär,
Assembler erlaubten Label mit 6 - 8 Zeichen Länge und waren ziemlich
lahm. Für einen Programmierer war es daher angenehm eindeutige
Sprungziele mit $+x abzukürzen, ohne wieder ein neues Label zu
verwenden.
Ein Kollege hatte einen Assembler (von Intel oder Philips), der selbst
bei kleinen Programmen auf einem IBM-PC Minuten für die abschließende
.hex-Datei brauchte.
Kompatibel zu anderen CPUs mußte man nicht sein - die übrigen
Unterschiede waren eh zu groß.
Martin H. schrieb:> Bei der Z80 ist die relative Endlosschleife 18 FE (JR -2), d.h. der PC> zeigt bei der Ausführung schon auf den nächsten Befehl.
Um den Maschinencode "18 FE" für "JR -2" zu erzeugen, schreibt man im
Assembler-Quellcode "JR $".
$ ist auf die Adresse des aktuell übersetzten Befehls, also die
Speicherzelle in der 0FEh für "JR" steht. Die "-2" im Befehl bezieht
sich auf den Program Counter in der CPU, der bei Ausführung des
JR-Befehls bereits auf das erste Byte des folgenden Befehles zeigt.
$ ist einfach nur ein spezielles Label für die Adresse des aktuellen
Befehls. Was daraus dann für ein Code generiert wird, darum kümmert sich
der Assembler.
Du kannst z.B. auch schreiben "LJMP $", dann enthält der Code die
aktuelle Adresse.
Besonders beim 8051 ist, daß er schon im Voraus das nächste Byte liest,
auch wenn es nicht ausgeführt wird. Hast Du z.B. an 0x1000 ein RET
stehen, holt er noch den Befehl von 0x1001 und springt dann an die
Adresse aus dem Stack zurück.
Peter D. schrieb:> Besonders beim 8051 ist, daß er schon im Voraus das nächste Byte liest,> auch wenn es nicht ausgeführt wird.
Diese Methode ist eigentlich recht verbreitet. Auch die AVR Cores halten
das so, und schon der 6502.
Praktisch (aber bisher von mir nicht beobachtet) wäre auch ein
Platzhalter für "nächster Befehl". Folgendes Konstrukt habe ich in
8051-Code schon häufig gesehen:
CJNE $REG #$VAL next_instruction
next_instruction:
J[N]C somewhere
Genutzt wird das als einfacher größer als/kleiner als-Vergleich. Dabei
ist es völlig egal, ob der Sprung ausgeführt wird oder nicht, es wird
sich nur zunutze gemacht, daß die Auswertung das Carryflag setzt/löscht,
es wird so oder so das J[N]C ausgeführt.
Klar, man könnte
CJNE $REG #$VAL $+3 schreiben, aber wehe, man vertippt sich da mal, und
schreibt aus Versehen +2 oder +4...
Nachdenklicher schrieb:> Praktisch (aber bisher von mir nicht beobachtet) wäre auch ein> Platzhalter für "nächster Befehl".
sowas lässt sich ganz einfach mit einem Macro bauen. Die Macro
Funktionen beim Keil A51 sind ziemlich mächtig.
Thomas Z. schrieb:> sowas lässt sich ganz einfach mit einem Macro bauen. Die Macro> Funktionen beim Keil A51 sind ziemlich mächtig.
Den hab ich nicht. ;-) Weiß nicht mal, ob es den für Linux gibt.
Aber der, den ich mir gerade selbst schreibe, wird so etwas haben. Auch
lokale Labels und so Späße. Wird nur aufgrund von begrenzter Zeit noch
ein Weilchen dauern. (Und ja, ich habe mit 8051 noch genug zu tun, daß
sich das für mich lohnt, neue Firm- für alte Hardware und so...)
Du schreibst in der heutigen Zeit noch einen Assembler für den 51er?
Du kannst da natürlich Features reinpacken wie du willst, ich gebe aber
zu bedenken dass die Quellen dann nur zu deinem Assembler passen. Ob das
so eine gute Idee ist?
Thomas Z. schrieb:> Du schreibst in der heutigen Zeit noch einen Assembler für den 51er?
Ja. Wie gesagt, ich arbeite damit im Sinne von "neue Firmware für alte
Hardware" noch recht viel. Und die Auswahl von guten 8051-Assemblern
(Betonung auf gut) für Linux ist doch eher... meh. Ist alles ein recht
spezieller Anwendungsfall, den zu erklären hier zu lange dauern würde.
Trust me, I know what I am doing. ;-)
Wir reden hier allerdings auch nicht von kommerziell und Broterwerb,
sondern von Feierabendbastelei. Deshalb steht Keil da auch nicht zur
Debatte, selbst wenn es den für Linux geben sollte, weil teuer. Beim
Brotwerwerb steht der Fokus auf ARM.
Thomas Z. schrieb:> Du kannst da natürlich Features reinpacken wie du willst, ich gebe aber> zu bedenken dass die Quellen dann nur zu deinem Assembler passen. Ob das> so eine gute Idee ist?
Ist an der Stelle egal, da der auch nur den Kram assemblieren muß, den
ich ihm vorsetze. Und das ist in der Regel reverse engineered, und im
Falle von in C geschriebenen Originalquellen auch oft so, daß die
meisten Disassembler streiken*, weshalb ich das dann doch überwiegend
manuell mache. Die Sorucen selbst verlassen meinen PC auch nur in Form
eines verschlüsselten Cloud-Backups, da wird also auch nie jemand
anderes dran gehen (und wenn doch, kriegt derjenige eben den Assembler
dazu).
*) C51 hat seine case-Anweisung beispielsweise so gestrickt, daß direkt
nach dem Funktionsaufruf die Sprungtabelle folgt, und der Rücksprung
dann in der aufgerufenen Funktion berechnet wird (Adresse der
Sprungtabelle liegt ja dann praktischerweise auf dem Stack). Da kommen
die Disassembler, die ich bisher probiert habe, nicht mit klar.
Insbesondere wenn die Interpretation der Sprungtabelle als Maschinencode
mit dem tatsächlichen Ende überlappt, und die ersten 1-2 Bytes des
folgenden Maschinencodes dann noch als Teil der disassemblierten
Sprungtabelle interpretiert werden, und dadurch echte Instruktionen
"verschwinden".
Nachdenklicher schrieb:> C51 hat seine case-Anweisung beispielsweise so gestrickt, daß direkt> nach dem Funktionsaufruf die Sprungtabelle folgt
ja das macht er aber nur wenn die case zusammenliegen zb 0..9.
die Libfunktion dazu ist dann ccase. In anderen fällen wird ein binärer
Baum geprüft.
Wenn man die ccase lib funktion gefunden hat ist die Disassemblierung
einfach.
braucht aber etwas Handarbeit. Ich arbeite oft mit IDA Pro und hab mir
Flirt Tabellen zu identifikation der Keil libs gebaut.
https://github.com/usbman01/KeilC51flirt
Ich kenn unter linux nur den Assembler aus dem SDCC Paket und ja der ist
nicht so toll.
Thomas Z. schrieb:> ja das macht er aber nur wenn die case zusammenliegen zb 0..9.> die Libfunktion dazu ist dann ccase. In anderen fällen wird ein binärer> Baum geprüft.
In Binaries, die ich in den Fingern hatten, waren das teilweise auch
Werte, die völlig durcheinander waren. Hängt vielleicht auch von der
Version ab?
Rein vom Ablauf her wäre es auch egal, da es immer Tupel
Wert/Sprungadresse sind, und er linear die Liste von oben nach unten
abarbeitet, bis entweder was passendes gefunden ist, oder nach einem
0x0000-Trenner die "Default"-Sprungadresse kommt. Da ich leider nichts
außer dem EPROM-Dump habe, weiß ich natürlich nicht, welche Version von
C51 verwendet wurde. Aber die bekannten Bibliotheksfunktionen und
Startup-Code sind ein sicheres Indiz, daß es C51 war. Allerdings wohl
mit sehr seltsamer Konfiguration, da ausnahmslos alle Daten immer im
XRAM lagen, abgesehen von Registern und Stack, die ja immer im IRAM
sind. Auch der Optimizer war wohl nicht besonders gut, an etlichen
Stellen wurde ein Wert erst ins XRAM gespeichert und sofort wieder aus
diesem geladen. Funktioniert, ist aber nicht besonders performant.
Insbesondere, wenn nichts dazwischenfunken und den Wert in A ändern
kann. ;-)
Thomas Z. schrieb:> Ich arbeite oft mit IDA Pro
Ist mir für Feierabendbasteleien zu teuer. ;-) Hatte mal Ghidra
probiert, aber da kam nichts wirklich sinnvoll nutzbares bei raus.
Nachdenklicher schrieb:> Allerdings wohl> mit sehr seltsamer Konfiguration, da ausnahmslos alle Daten immer im> XRAM lagen
wenn das so ist wurde im Large Mode übersetzt. Für mich eindeutig ein
Zeichen dass derjenige keine Ahnung vom Keil hatte oder im das Ergebnis
egal war.
Man kann auch im small Mode problemlos Variablen im XDATA plazieren.
Der Keil Optimizer ist immer noch der Beste den ich bisher für x51
gesehen habe. Da kommt nach meinen Erfahrungen weder IAR noch SDCC ran.
Die Lib Funktionen sind eigentlich seit v3 weitgehend gleich geblieben.
Der Compiler selbst wurde sicher seit der Umstellung auf 32Bit nicht
mehr angefasst. Was sich später noch geändert hat waren spezielle
Features bei bei bestimmten Targets. Das sind dann in der Regel
spezialisierte Libs
Das ist aber hier jetzt OT
Thomas Z. schrieb:> wenn das so ist wurde im Large Mode übersetzt.
Ja, das Konzept des 8051 mit den verschiedenen Speicherbereichen haben
viele nicht verstanden. Die Idee war schon recht clever, bis zu 128 Byte
direct data schnell und mit kurzem Code zugreifen zu können. Ich habe in
keinem Projekt die 64kB Flash ausfüllen können. Banking habe ich also
nie benutzen müssen. Schade, daß die Typen mit 8kB SRAM (P89C668,
AT89C51RE2) nicht mehr hergestellt werden.
Nachdenklicher schrieb:> *) C51 hat seine case-Anweisung beispielsweise so gestrickt, daß direkt> nach dem Funktionsaufruf die Sprungtabelle folgt
Das hatte ich zu Assemblerzeiten auch sehr oft so gemacht. Dadurch wird
der Code deutlich kompakter, da ich vor dem Aufruf keine Register laden
muß oder den Stack. Auch ist es besser lesbar, wenn ein konstanter Text
direkt nach dem Aufruf steht und nicht ganz woanders in einem
Datensegment.
Peter D. schrieb:> Die Idee war schon recht clever, bis zu 128 Byte> direct data schnell und mit kurzem Code zugreifen zu können.
Dieser Teil war sehr clever. Die zweite Hälfte zu den 256 Bytes auch
noch. Das war damals recht viel für einen Mikrocontroller. Mehr war
eigentlich nicht vorgesehen gewesen. Man baute damals nicht fürs
Jahrhundert, sondern auf kurze Sicht.
Jenseits von 256 Bytes liess die Cleverness dann etwas nach, weil die
nervenden Kunden unbedingt einen 8051 mit einigen kB RAM haben wollten,
statt des doch viel besseren 8096.
Intel (80C251) und Philips (80C51XA) hatten mal versucht, einen zum 8051
kompatiblen 16-Bitter rauszubringen. Haben aber beide keine Verbreitung
gefunden.
Ich hab noch einen 80C251 samt Debugger über den LPT-Anschluß rumliegen.
Beides aber nie benutzt.
Der Keil C251 Dongle könnte auch noch rumliegen. Nur habe ich keinen
LPT-Port, um ihn anzuschließen.
Peter D. schrieb:> Intel (80C251) und Philips (80C51XA) hatten mal versucht, einen zum 8051> kompatiblen 16-Bitter rauszubringen.
Als Nachfolger für den 8051 (oder genauer gesagt 80C552) hatte ich den
SAB80C166 im Auge, was sich dann aber zerschlagen hat.
> Der Keil C251 Dongle könnte auch noch rumliegen. Nur habe ich keinen> LPT-Port, um ihn anzuschließen.
Bei der DOS-Version mußte man nach einem bestimmten Muster im Programm
suchen und einen Befehl durch NOP ersetzen. Dann hatte sich die Dongelei
erübrigt ;-)
m.n. schrieb:> Der Offset von +4 wird also als +2 kodiert.
Ähem... nö.
Der Assembler weiß, daß der PC bereits auf den nächsten Befehl gestellt
ist und daß der Relativsprung bei diesem Chip 2 Bytes lang ist. Folglich
zieht er von dem gewünschten Offset 2 ab. So kommt dann diese 2 dabei
heraus.
So machen es relativ viele Architekturen. Putzig war es nur bei den
älteren ARM-Architekturen, wo man noch nicht sowas wie "RETURN"oder
"RETI" hatte und stattdessen sowas schreiben mußte "SUBS PC,LR,#4".
Dennoch ist es allemal besser mit benamten Sprungzielen anstelle
numerischer Offsets zu arbeiten. Egal ob da nun der Assembler lokale
Marken kennt oder nicht.
W.S.
W.S. schrieb:> Folglich> zieht er von dem gewünschten Offset 2 ab. So kommt dann diese 2 dabei> heraus.
Wieder einmal muß ich mich über Dich wundern. Nichts anderes habe ich
geschrieben.
m.n. schrieb:> Wieder einmal muß ich mich über Dich wundern.
Und so wundern wir uns gegenseitig über uns.
Aber dein
m.n. schrieb:> Der Offset von +4 wird also als +2 kodiert.
ist schlichtweg falsch, denn es gibt keinen "Offset von +4". Richtig
wäre "der Offset von +2 wird also als +2 kodiert". Toll!
Was du meinst, ist wohl das Wissen der Hersteller des Assemblers, daß
dieses $ nicht den Stand des PC zum tatsächlichen Ausführungszeitpunkt
darstellt, sondern den Beginn des Befehls.
Ach, wir haben heuer neuere Kamellen. Laß die alten ruhen.
W.S.