Assembler: Farben zählen

  • Im Thema RE: Assembler - Fortsetzung

    unter Beitrag #8

    hatte Andy diese Programm erarbeitet:

    Es fertigt eine Staistik über alle in einer Bitmap-Datei vorkommenden Farben an.

    Hier eine kleine Beispieldatei: rot1.bmp

    In dem Programm ist ein fertiger $binarycode eingefügt (Zeile216) , der von der Fuktion DllCallAddress aufgerufen wird. Das klappt auch einwandfrei und die Statistik wird angezeigt.

    Wird der $binarycode jedoch neu erzeugt, so funktioniert es nicht. Am besten kann man das bewerkstelligen, indem man unter der Zeile216 : $binarycode = "0x8B7C24048B4C240.....

    die beiden Befehle

    $binarycode = _AssembleIt2("retbinary", "_countpixelcolors")
    MsgBox(0,"",$binarycode)

    einfügt Der dabei entstehned $binarycode unterscheiden sich von dem in Zeile216 eingetragenen hinten um die letzten Bytes.

    Wo steck der Fehler?
    Vielen Dank schon einmal für eine Antwort!

  • Hi,

    da sind in deinem asm-code wohl einige Zeilen verloren gegangen :o)

    @end:
    pop ebx
    rdtsc
    sub eax,ebx

    ret

    Füge die 3 Zeilen zwischen dem @end: und ret dazu und alles läuft...

    Nur zur Erklärung, ich nutze zum "Takte zählen" den (bzw. einen der) Prozessorinternen TimeStampCounter, also einen Timer. RDTSC aka ReaDTimeStampCounter

    Der schreibt den aktuellen timestamp als 64 Bit "Timer" in die Register EAX und EDX. Ich nutze für kurze Schleifen nur die unteren 32Bit, also das EAX-Register.

    Dieses wird am Anfang des Codes auf den Stack gepushed, am Ende des Codes wird durch RDTSC wieder der neue Timestamp ausgelesen.

    POP EBX holt den alten Wert vom Stack

    RDTSC schreibt den aktuellen Timerwert in EAX, Info dazu https://www.felixcloutier.com/x86/rdtsc übrigens ein feines Nachschlagewerk..

    SUB EAX,EBX ermittelt die Differenz, also die Anzahl der Prozessortakte, in EAX

    Nach dem RET wird EAX an das aufrufende Programm, also AutoIt, zurückgegeben und kann angezeigt werden. Das macht übrigens der erste Parameter, das "uint" in AssembleIt. Beim DllCalladdress() werden der Rückgabewert und die Aufrufparameter in einem Array zurückgegeben.


    Übrigens, wenn Du Interesse hast, ich hatte im letzten Jahr AssembleIt noch etwas weiter aufgebohrt und etliches gefixt.

    Jetzt gibt es auch ein rudimentäres "How to", schau mal rein und sag mal was du davon hälst...

    AssembleIt2_64 v 313.zip

    In den Dateien Example1 bis Example 5 wird haarklein erklärt welche Möglickeiten AssembleIt hat, eins der "neuen" Features ist bspw. durch Anhängen von

    ,"retbinary",@ScriptlineNumber) an den Funktionsaufruf von AssembleIt2 wird der Aufrufcode für DllCallAddress() als einige Zeilen Code generiert und in die Konsole und ins Clipboard geschrieben, hier mal als Beispiel:

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy (24. März 2024 um 13:16)

  • Hallo Andy,

    VielenDank für Deine Antwort.

    Ich hatte ja damals den $binarycode aus deinem Programm in mein Bitmapprogramm mit großem Erfolg eingebaut. Die Rechenzeit hatte sich um den Faktor 500 verkürzt.

    Aber ein Mangel bestand noch, dass die schwarzen Pixel (also Farbwert 0x000000) nicht erkannt wurden. Der Sache möchte ich nun auf den Grund gehen.

    Vielen Dank für die neue Variante von AssembleIt2_64 v 313.zip. ZurZeit arbeite ich mit der von dir unter

    <RE: AssembleIt2_64 incl. Debugger uvm...>

    veröffentlichten Variante.

  • Aber ein Mangel bestand noch, dass die schwarzen Pixel (also Farbwert 0x000000) nicht erkannt wurden.

    Hmmmm....DAS muss ich auch untersuchen....schön, wenn man Code dokumentiert...habs sofort gefunden :o)

    Code
        @count_colors:
        sub edx,1                   ;jedes pixel
     ;   jz @end                     ;alle pixel durchlaufen
        mov ecx,[esi+edx*4]         ;ecx=anzahl, edx=farbwert; esi=pointer pixelstruct
        cmp ecx,0
        je @count_colors            ;wenn ungleich null...ist farbe gefunden

    ich hatte schon bei Farbe = 0x000001 aufgehört, lass mal das jz @end weg und

    ersetze das Ende der Schleife mit

    Code
    	cmp edx,0					;ende erreicht?
        jne @count_colors           ;nein, dann weiter mit schleife alle farben

    Das müsste es gewesen sein....teste mal bitte und gib Rückmeldung :party:

    **EDIT

    besser das gesamte Script:

  • Total gut !!!!!!!!!!!!

    Wie schnell du das gefunden hast. Mit 89 ist man eben etwas langsamer.

    Ganz bequem übernehme ich nun meinem inneren Schweinehund gahorchend den Binarycode so wie er ist in mein Bitmap-Programm.

    Ich habe ja noch ein eigentlich einfaches Bitmap-Problem. Das verrate ich dir aber nicht. Sonst hast du es im Nu gelöst und ich lerne nichts hinzu :) . Wenn ich es aber dann gelöst habe, werde ich es in dir in diesem Thema miteilen. Wie ich dir damals schon gesagt hatte, habe ich Jahrzehnte Assembler programmiert. Aber es waren ja viel einfacher Rechner. Das hat heute ganz andere Dimensionen und das "Handwerkszeug" ist doch erheblich umfangreicher geworden. Jetzt habe ich mir erst einmal ein Rahmenprogramm geschrieben, das es mir beim Test leicht macht, zwischen den einzelnen Arbeitsphasen

    _AssembleIt2("uint ..." ,

    _AssembleIt2("retbinary ... " ,

    nur DllCallAddress("uint:cdecl ... " ohne _AssembleIt

    umzuschalten.

    Recht vielen Dank für deine Mühe!

    Gruß DOheim

  • Ich habe ja noch ein eigentlich einfaches Bitmap-Problem. Das verrate ich dir aber nicht. Sonst hast du es im Nu gelöst und ich lerne nichts hinzu :)

    Nanana....ich tüftele auch gerne, aber manchmal ist der Weg das Ziel! Mit dem "Weg" meine ich den gegenseitigen Austausch! Wie du sicher weißt, sind schon etliche der "anderen" Dinosaurier ausgestorben. Daher freue ich mich immer wie ein kleines Kind, wenn jemand an diesem "Oldschool Assembler Gedöns" auch (noch) seinen Spass hat.

    Wobei, um ehrlich zu sein, ich bemitleide ehrlich gesagt diese armen Schweine, die heute vor der Glotze sitzen und in irgendwelchen "neumodischen" esoterischen Sprachen sog. "Hightech" verzapfen und dann, wenn etwas nicht funktioniert, das Internet mit Hilfeanfragen fluten. Seltsamerweise sind dann die "Helfer" (fast) immer irgendwelche "alten Säcke", die sich (natürlich) sowohl mit dem altmodischen Kram aus dem Computerpleistozän auskennen, als auch mit dem neumodischen Gedöns:party:.

    Obwohl auch junge Leute unglaublich fit sind. Ich bekomme netterweise immer mal wieder Berichte aus Jülich vom dortigen Forschungszentrum und HPC (High-Performance-Computing) zugespielt und war auch schon dort und konnte mich mit einigen der "richtigen" Programmierer dort unterhalten. Da weiß jeder, wie er(sie) die Maschine codetechnisch zu füttern hat! Und ja, dort werden auch die letzten Register in den zig-tausenden Prozessoren ausgenutzt, um das letzte Quentchen Leistung rauszukitzeln. Ohne UMFASSENDE Assemblerkentnisse bzw. detailliertes Know-How über die internen Rechnerstrukturen ist auch keine "High-Performance" zu erzielen.


    Jetzt habe ich mir erst einmal ein Rahmenprogramm geschrieben, das es mir beim Test leicht macht, zwischen den einzelnen Arbeitsphasen

    _AssembleIt2("uint ..." ,

    _AssembleIt2("retbinary ... " ,

    nur DllCallAddress("uint:cdecl ... " ohne _AssembleIt

    umzuschalten.

    :party:.....würdest du dieses "Rahmenprogramm" ggf. mit uns anderen "Dinosauriern" teilen oder wenigstens kurz beschreiben was es macht?!:Glaskugel:


    Zitat

    Zitat von UEZ

    Echt jetzt? 8|

    Ja, ja - er ist der letzte Zeitzeuge der Schlacht von Waterloo. 😝

    =O.....du Sack8o...jetzt fühl ich mich alt.....:theke:...obwohl ich mich vor ner Stunde erst für 45min an meinen Klimmzugturm zum Schwitzen gebracht hatte....Calisthenics FTW!

  • Etwas off-topic, verzeiht mir, doch ich muss kurz meine Begeisterung zum Ausdruck bringen:

    ...obwohl ich mich vor ner Stunde erst für 45min an meinen Klimmzugturm zum Schwitzen gebracht hatte....Calisthenics FTW!

    Wenn dies dein Ernst war Andy => Calisthenics => Hut ab 👍 !

    Mit 89 ist man eben etwas langsamer.

    Falls dem so ist DOheim , wünsche ich dir weiterhin stetige Gesundheit 😇 .
    Freut mich das du noch so fit bist was etwaige Entwicklungstätigkeiten angeht - dies erhoffe ich mir für mich auch, doch wer weiß schon was das Leben so bringt.

    ==> end of off-topic

    Viele Grüße
    Sven

  • Vielen Dank für eure netten Antworten.

    Ich habe an meinem Rahmenprogramm noch etwas „herumgeschneidert“ und hoffe, dass es nun auch in fremder Umgebung funktioniert.

    Statt über $CmdLine die Phase einzustellen, ist es einfacher den Wert in Zeile 22 einzutragen.

    Protokoll() ist mein Autoit-Debugger. Bitte nicht daran stören.


    Ich habe noch eine Frage:

    Wäre es möglich, im Assemblerprogramm den Debugger-Aufruf z.B. so zu gestalten

    Stelle3) _asmdbg_()

    und dann im Debugger-Fenster die Zeichenfolge Stelle3 anzuzeigen? Das würde ungemein helfen.


    Hier mein Rahmenprogramm:

    Einmal editiert, zuletzt von DOheim (30. März 2024 um 17:52)

  • Ich habe noch eine Frage:

    Wäre es möglich, im Assemblerprogramm den Debugger-Aufruf z.B. so zu gestalten

    Stelle3) _asmdbg_()

    und dann im Debugger-Fenster die Zeichenfolge Stelle3 anzuzeigen? Das würde ungemein helfen.

    Wenn du Scite als Editor verwendest, springt der Debugger in die Zeile im Script, in der sich der Debuggeraufruf aktuell befindet. Du siehst auch die blauen Klammern hinter _asmdbg_()

    Für das debuggen ist es also sehr hilfreich das Debuggerfenster so zu plazieren, dass der Scriptcode in Scite nicht verdeckt ist.

    Dann kannst du, wenn du im Debuggerfenster auf NEXT klickst, den Schritt zum nächsten _asmdbg_() mitverfolgen.

    Außerdem steht in der Kopfzeile des Debuggerfensters, in welcher Zeile in Scite sich der Debugger aktuell befindet

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy (1. April 2024 um 06:34)

  • Hallo Andy,

    das Programm „Farben zählen“ hat ja nun auch die schwarzen Pixel erkannt.

    Ich wunderte mich, dass es aber bei manchen Bitmapdateien abgebrochen ist und habe herausgefunden, dass die Bitmapdatei mindestens ein schwarzes Pixel besitzen muss.

    Ich habe hinter der Sprungmarke

    @count_colors:

    die beiden Befehle eingefügt:

    sub edx,0

    jz @end

    Nun ist alles in Butter.

    #FarbenZaehlen.au3

    Eine Frage habe ich noch:

    Im Debugger-Fenster werden bei mir die übergebene Parameter um eine Position verschoben angezeigt. Z.B. die Werte von width und height stehen eigentlich in [esp+8] und [esp+12] werden aber im Debugger-Fenster unter [esp+12] und [esp+16] angezeigt.

    Habe ich etwas falsch gemacht?

  • Hallo DOheim,

    Ich wunderte mich, dass es aber bei manchen Bitmapdateien abgebrochen ist und habe herausgefunden, dass die Bitmapdatei mindestens ein schwarzes Pixel besitzen muss.

    hmmm, scheinbar hatte ich das verbockt....so ist das, wenn man auf die Schnelle etwas umbaut, mit einigen Dateien testet und dann doch irgendwann ein Fehler auftritt:P

    Jetzt läuft es dank dir doch rund:party:

    Im Debugger-Fenster werden bei mir die übergebene Parameter um eine Position verschoben angezeigt. Z.B. die Werte von width und height stehen eigentlich in [esp+8] und [esp+12] werden aber im Debugger-Fenster unter [esp+12] und [esp+16] angezeigt.

    Nein, du hast nichts falsch gemacht!:thumbup:

    Ja, das kommt durch das PUSH EAX nach dem RDTSC (das liest einen Zeitstempel der seit dem Computerstart vergangenen Prozessortakte nach EDX:EAX aus. Damit wird der aktuelle Zeitstempel (der untere, für uns relevante Teil steht in EAX ) ausgelesen). Ich nutze das RDTSC ausschliesslich zum "Takte zählen":Glaskugel:. Für den Programmablauf braucht man das definitiv nicht!

    Das PUSH EAX schiebt EAX "oben" auf den Stack, deshalb wird EAX dort eingefügt. Der Stack wird zum Speichern lokaler Variablen [ESP+12] und für Rücksprungadressen verwendet.

    PUSH erhöht auch den Stack Pointer ESP , das ist der Zeiger, mit dem du alle Elemente auf dem Stack ansprechen kannst.

    Schreib mal ein zusätzliches _asmdbg_() vor das RDTSC, dann siehst du, was passiert.

    Wichtig in diesem Zusammenhang ist, dass bei jedem Funktionsaufruf an den Prozessor, also CALL oder auch eine Funktionsaufruf einer AutoIt-Funktion oder jedweger anderer Programmiersprachenfunktion die aktuelle Speicheradresse ALS ERSTER PARAMERTER (das ist die sog. "Rücksprungadresse") IMMER zuerst auf den Stack geschrieben wird!!! Das Programm arbeitet die Funktion ab und muss nun irgendwie in das Hauptprogramm zurückkommen, günstigstenfalls an die Speicherstelle, von der aus die Unterfunktion aufgerufen wurde! Diese Speicheradresse wird also gesichert, damit der Prozessor den Weg zurück ins Hauptprogramm findet. Im Debugger ist das [ESP+00], also die Speicherstelle, auf die der Stackpointer zeigt. Beim Start des Programms ist dort die "Rücksprungadresse" (in das aufrufende Programm). Danach folgen dann die Funktionsparameter.

    Das ESP-Register ist also nur ein Zeiger auf den Stack. Du kannst bspw. mit SUB ESP,100 dir 100 Bytes auf dem Stack als Speicherplatz reservieren. Dann kannst du mit mov [ESP+76] ,0xFFAABBCC dort einen Wert reinschreiben und benutzen. Am Ende deines Programms ist nur wichtig, dass das ESP-Register auf die gleiche Adresse zeigt wie beim Start des Programms. Deshalb gehört zu jedem PUSH im Programm auch das entsprechende POP. Oder zum SUB ESP,100 das entsprechende ADD ESP,100.

    Wie du jetzt gemerkt hast, ändert sich der Inhalt der (Stack-)Speicherstelle [ESP+12] mit jedem PUSH oder POP in deinem Programm. Die Reihenfolge der Parameter auf dem Stack ändert sich nicht!!

    Du kannst ja mal eine Sequenz von folgenden Befehlen auf dem Stack beobachten um zu sehen, wie das Prinzip funktioniert.

    Obwohl die Register EAX,EBX und ECX im Programmablauf mit 0 überschrieben wurden, sind am Ende des Programms die ursprünglichen (auf den Stack mit PUSH "gesicherten" Werte) per POP wieder zurückgeschrieben worden.


    Man muss sich nur EINE Regel im Kampf mit dem Stack merken: Der Speicherinhalt an [ESP] muss am Ende des Programms der gleiche sein wie am Anfang des Programms: die Rücksprungadresse in das aufrufende Programm!

    (Zu jedem PUSH gehört das entsprechende POP)

    Und da auf dem Stack ALLE Rücksprungadressen aller Funktionen (die wiederum Funktionen aufrufen die innerhalb von Schleifen Funktionen in Funktionen aufrufen) stehen, sollte man wissen was man tut. An "irgendwelche" Adressen auf dem Stack zu schreiben (die vorher nicht entsprechend reserviert wurden) führt unweigerlich zum Crash!


    Wenn man Programme schreibt, die den Stack (pointer) nicht verändern, kann man im Programmablauf natürlich auch [ESP+12] als Wert des Übergabeparameters verwenden bzw diesen Wert auch verändern/überschreiben und so als Speicherstelle benutzen!


    Du kannst also das RDTSC und PUSH EAX am Anfang des Programms einfach entfernen, musst dann aber auch das POP EBX am Ende des Programms entfernen. Dann sieht der Stack auch während des Programmablaufs genau so aus, wie am Anfang!:party:

    Eine verständliche Erklärung vom Stack findet man bspw. hier https://www4.cs.fau.de/Lehre/WS09/V_B…ler.shtml#stack

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    2 Mal editiert, zuletzt von Andy (15. Mai 2024 um 21:21)