Variable wird immer überschrieben



  • Irgendwie Blicke ich da bei den Variablen nicht durch.

    .486                                     
    .model flat, stdcall                     
    option casemap :none 
    
    include     \masm32\include\windows.inc
    include     \masm32\include\kernel32.inc
    includelib  \masm32\lib\kernel32.lib
    include     \masm32\include\masm32.inc
    includelib  \masm32\lib\masm32.lib
    include \masm32\include\user32.inc 
    includelib \masm32\lib\user32.lib 
    
    .data
    
    nr dw 123
    ; nr2 db 489 Wenn nr2 hier deklariert ist dann stürtzt das Programm ab
    
    .code
    start:
    
    	mov eax ,  dword ptr [nr]
    	mov esi,offset nr+2
    
    	mov ebx,10
    i:
    	xor edx,edx
    	div ebx
    	add dl,30h
    	mov [esi],dl
    	dec esi
    	cmp eax,0
    jne i
    
    	invoke StdOut, addr nr
    
    	invoke Sleep,1000
    	invoke ExitProcess,0
    
    end start
    

    Wenn die Variable nr2 nicht deklariert ist dann funktioniert das Programm ohne Probleme.

    Und nochmal zu der Sache mit dem define byte.

    Hier in meinem Programm kann ich ja auch schreiben: nr db 123

    Und mov eax , dword ptr [nr] verwenden es funktioniert, macht der Assembler da dann eine Typkonvertierung?



  • Du lädst eine DWORD obwohl nur ein WORD deklariert ist. Die Bytes über nr sind mit 0 installiert (gehören eigentlich nicht zu deinem Prog.), so dass du 123 lädst. Wenn du nun aber ein weiteres Byte deklarierst, wird der Wert in eax entsprechend größer (489 passt nicht in ein Byte!). Da dein 'Buffer', den du durch <offset nr+2> referenzierst, aber nur 4 Byte groß ist, schreibet deine Schleife schon fleißig an Adressen unterhalt von nr, so dass ein Zugriffsverletzung auftritt.

    include \masm32\include\masm32rt.inc
    .data 
    nr dd 123 	; dd = declare double word = 32 bit
    sz db 11 dup (?)            
    .code 
    start: 
        mov eax ,  dword ptr [nr] 
        mov esi,OFFSET sz[9]   ; = OFFSET sz + 9
    
        mov ebx,10 
    i: 
        xor edx,edx 
        div ebx 
        add dl,30h 
        mov [esi],dl 
        dec esi 
        cmp eax,0 
    jne i 
    
    	inc esi
        invoke StdOut, esi
        invoke StdOut,chr$(13,10) ; Zeilenumbruch
    
        inkey
        invoke ExitProcess,0 
    
    end start
    

    var_var schrieb:

    dword ptr [nr] verwenden [...] macht der Assembler da dann eine Typkonvertierung?

    ja, durch das "DWORD ptr" überschreibst du den Typen des labels nr.



  • Hallo danke für eure Antworten, jetzt wird mir klar wieso das nicht funktionierte was ich da gebaut habe.

    Hier ist meine korrektur:

    .486                                     
    .model flat, stdcall                     
    option casemap :none 
    
    include     \masm32\include\windows.inc
    include     \masm32\include\kernel32.inc
    includelib  \masm32\lib\kernel32.lib
    include     \masm32\include\masm32.inc
    includelib  \masm32\lib\masm32.lib
    include \masm32\include\user32.inc 
    includelib \masm32\lib\user32.lib 
    
    .data
    
    nr dd 489  ; int 
    nr2 db 255  ; Die größte Dezimalzahl die mit db dargestellt werden kann ist 255
    
    .code
    start:
    
        mov eax ,  dword ptr [nr]
        mov esi,offset nr+2
    
        mov ebx,10
    i:
        xor edx,edx
        div ebx
        add dl,30h
        mov [esi],dl
        dec esi
        cmp eax,0
    jne i
    
        invoke StdOut, addr nr
    
        invoke Sleep,1000
        invoke ExitProcess,0
    
    end start
    

    Ich habe mir mal diese Tabelle gemacht ist das so richtig?

    db = byte 8bit z.b. al char
    dw = word 16bit z.b. ax short
    dd = dword 32bit z.b. eax int

    Zahlen brauchen in Assembler keinen Nullterminator nur ein String brauch das Beispiel: var db "hallo",0 das ist doch auch richtig oder?



  • var_var schrieb:

    Hier ist meine korrektur:...

    Das ist ziemlich abenteuerlich und funktioniert in diesem Fall eigentlich nur zufällig. Weißt Du denn genau, was das Programm macht?

    db = byte 8bit z.b. al char
    dw = word 16bit z.b. ax short
    dd = dword 32bit z.b. eax int

    Noch überall ein "unsigned" vor die C-Bezeichnungen (char, short, int), dann ist es richtig. C-Puristen werden einwenden, dass die C-Datentypen keine bestimmte Bitbreite haben.

    Zahlen brauchen in Assembler keinen Nullterminator nur ein String brauch das Beispiel: var db "hallo",0 das ist doch auch richtig oder?

    Jein. Assembler selbst kennt keine Strings, sondern nur eine Ansammlung von Bytes. Mit 'invoke' rufst Du ein Unterprogramm auf und übergibst Parameter. Welche Parameter das Unterprogramm verarbeiten kann, hat der Programmierer des Unterprogramms seinerzeit festgelegt. In diesem Fall rufst Du mit 'invoke StdOut, addr nr' das Unterprogramm StdOut der Masm32.lib auf und übergibst einen Zeiger ('addr') auf die Variable 'nr'. Was genau nun das Unterprogramm erwartet, erfährst Du in masmlib.chm: "StdOut proc lpszText:DWORD". 'lpsz' steht für 'long pointer string zero-terminated'. Du musst also einen 32-Bit-Zeiger ('long pointer') auf eine Ansammlung von Bytes, die als ASCII-Zeichen interpretiert werden können (string) und mit 0 abschließen (zero-terminated) übergeben. Das ist aber nicht immer so. Bei folgendem Programm wird kein solcher String (mit Null am Ende) übergeben, sondern nur die Zeichenfolge und deren Länge:

    .386
    .Model Flat, stdcall
    option casemap :none
    
    include \masm32\include\windows.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\kernel32.lib
    
    .DATA
    	Hallo db 'Hallo Welt!'
    	HalloLen EQU $-Hallo
    	lpNrOfChars dd ?
    
    .CODE
    Start:
    	invoke GetStdHandle, STD_OUTPUT_HANDLE
    	invoke WriteFile, eax, ADDR Hallo, HalloLen, ADDR lpNrOfChars, 0
    	invoke ExitProcess, 0
    END Start
    

    viele grüße
    ralph



  • rkhb schrieb:

    Das ist ziemlich abenteuerlich und funktioniert in diesem Fall eigentlich nur zufällig. Weißt Du denn genau, was das Programm macht?

    Ja ich weiss was das Programm macht. Der Wert der im EAX Register steht wird so oft durch 10 geteilt bis er 0 ist. Bei der Division von EAX = EAX / EBX ensteht ein Rest dieser Rest steht nach der Division im dl Register.

    Und das was im dl Register ist schiebe ich nach ESI, ESI zeigt auf meine Variable nr dd wobei mir gerade auffällt das ich die Ergebnisse aus dl in einer Extra Variable speichern wollte. 🤡

    Also so:

    .486                                     
     .model flat, stdcall                     
     option casemap :none 
    
     include     \masm32\include\windows.inc 
     include     \masm32\include\kernel32.inc 
     includelib  \masm32\lib\kernel32.lib 
     include     \masm32\include\masm32.inc 
     includelib  \masm32\lib\masm32.lib 
     include \masm32\include\user32.inc 
     includelib \masm32\lib\user32.lib 
    
     .data 
    
    nr dd 489  ; int 
    
    var dd ?
    
     .code 
     start: 
    
         mov eax ,  dword ptr [nr] 
         mov esi,offset var+2 
    
         mov ebx,10 
     i: 
         xor edx,edx 
         div ebx 
         add dl,30h 
         mov [esi],dl 
         dec esi 
         cmp eax,0 
    jne i 
    
         invoke StdOut, addr var 
         invoke Sleep,1000 
         invoke ExitProcess,0 
    
     end start
    

    Aber warum ist das nicht gut wenn ich das so mache?



  • var_var schrieb:

    Aber warum ist das nicht gut wenn ich das so mache?

    1. nr ist als DWORD definiert und als INT kommentiert. Wenn Du in einem halben Jahr die mittlere Entfernung Erde-Sonne (149600000 km) ausgeben lassen willst, dann wird der Assembler nicht meckern. Du erntest aber einen grandiosen Absturz. Weißt Du warum?

    2. Die kleinste Mondentfernung (363300 km) verursacht zwar keinen Absturz, gibt aber lediglich '300' aus. Weißt Du warum? Gleichzeitig wird der Inhalt von nr verändert. Weißt Du warum?

    3. StdOut erwartet einen nullterminierten String. Dass hinter var im Augenblick eine Null folgt, ist reiner Zufall. Wenn Du in einem halben Jahr eine weitere Variable dahinter belegst, dann bekommst Du unerwartete Ausgaben.

    EDIT: Und schon bin ich beim Lesen hereingefallen: DD sind 4 Bytes und das Programm ist so programmiert, dass das letzte Byte unberührt bleibt. Damit MUSS aber nr eine dreistellige Dezimalzahl sein (100 <= nr <=999). Wer soll denn das in einem halben Jahr noch wissen?

    1. Weißt Du, welche drei inkludierten Dateien überflüssig sind?

    2. Was soll das Sleep?

    viele grüße
    ralph



  • Die Größe des Puffers ist: Maximalwert(DWORD) = 4294967295 = 10 Ziffern -> 10+1(term. zero) = 11 Byte
    Die Erste Ziffer wird an die Stelle <OFFSET Buffer+9> geschrieben.
    Ist der Puffer zu klein, besteht die Gefahr, dass es zu einen "buffer overflow" kommt.

    PS: mehr zu inkludieren als man derzeit brauch, schadet nicht -> masm32rt.inc mach einem das Leben deutlich einfacher 😉



  • mov esi,offset var+5 ; so kann 363300 auch ausgeben werden
    mov esi,offset var+8 ; so kann 149600000 ausgeben werden

    Elemente : 01234567
    Variable var: 00000000 363300 / 10 = 36330 in dl bleibt die 0 über (der Rest)
    Und diese 0 schreibe ich dann in var[7] das geht dann halt so lange weiter bis die letzte Zahl in var steht.

    Btw. dword steht ja für define word. nr ist aber als double word definiert, und ich schreibe aber: mov eax , dword ptr [nr] dann ist das dword doch falsch oder?

    Ich kenne bisher nur: byte ptr, word ptr und dword ptr

    Das Sleep hab ich nur drin damit ich sehe was das Programm ausgibt, dann muss ich es nicht immer über Konsole starten, ich hab auf meinem Desktop eine Batch Datei worüber ich das Programm assembliere.

    Ich hab nochmal etwas darüber nachgedacht was an meinem Programm schlecht ist:

    Lösungs Ansatz 1

    .486                                     
     .model flat, stdcall                     
     option casemap :none 
    
     include     \masm32\include\kernel32.inc 
     includelib  \masm32\lib\kernel32.lib 
    
     include     \masm32\include\masm32.inc 
     includelib  \masm32\lib\masm32.lib 
    
     .data 
    
    nr dd 489  ; int 
    
    var dd ?
    
     .code 
     start: 
    
         mov eax ,  dword ptr [nr] 
         mov esi,offset var + Anzahl der Zeichen die nr enthält 
    
         mov ebx,10 
     i: 
         xor edx,edx 
         div ebx 
         add dl,30h 
         mov [esi],dl 
         dec esi 
         cmp eax,0 
    jne i 
    
         invoke StdOut, addr var 
         invoke Sleep,1000 
         invoke ExitProcess,0 
    
     end start
    

    Ich könnte mir ja eine Funktion schreiben womit ich ermitteln kann wie viele Zeichen in nr stehen.

    Z.b. so:

    Ich teile nr so oft durch 10 bis eax 0 ist bei jedem Schleifendurchlauf könnte ich ja z.b. im ecx Register zählen lassen wie oft die Schleife durchlaufen wurde. Dann weiss ich ja hinterher wie viele Zeichen in der Variable nr stehen.
    Aber ist das eine gute Idee das so zu machen?

    Mein zweiter Ansatz:

    Ich schreibe den Rest aus dl einfach in das erste Element von nr.
    Das Problem die Ausgabe erfolgt rückwärts, und dafür jetzt wieder eine Funktion zu schreiben die das ganze vorwärts ausgibt ist warscheinlich auch nicht optimal oder?

    .486                                     
     .model flat, stdcall                     
     option casemap :none 
    
     include     \masm32\include\windows.inc 
     include     \masm32\include\kernel32.inc 
     includelib  \masm32\lib\kernel32.lib 
     include     \masm32\include\masm32.inc 
     includelib  \masm32\lib\masm32.lib 
     include \masm32\include\user32.inc 
     includelib \masm32\lib\user32.lib 
    
     .data 
    
    nr dd 363300  ; integer 32bit  
    var dd ?,0
    
     .code 
     start: 
    
         mov eax , dword ptr [nr] 
         mov esi,offset var 
    
         mov ebx,10 
     i: 
         xor edx,edx 
         div ebx 
         add dl,30h 
         mov [esi],dl 
         inc esi 
         cmp eax,0 
    jne i 
    
         invoke StdOut, addr var 
    
         invoke Sleep,1000 
         invoke ExitProcess,0 
    
     end start
    

    Was wäre die beste Lösung?



  • Oops bei Lösungsansatz 1 habe ich bei var dd ? den Nullterminator vergessen und mein Kommentar bei nr nicht geändert in: ; integer 32bit



  • Da es bei dir wohl noch einige Verwirrung gibt:
    db = decalre BYTE
    dw = declare WORD
    dd = declare DWORD
    dq = declare QWORD

    BYTE = unsigned char = 8 Bit
    WORD = unsigned short = 16 Bit
    DWORD = Double WORD = unsigned long = 216Bit = 32Bit
    QWORD = Quad WORD = unsigned long long = 4
    16 Bit = 64 Bit

    Für BYTE, WORD, DWORD und QWORD gibt es auch jeweils das Vorzeichenbehaftete Gegenstücke:
    SBYTE = char = 8 Bit
    SWORD = short = 16 Bit
    SDWORD = long = 32 Bit
    [[SQWORD = long long]](ml.exe v8+)

    Die Bedeutung von PTR ist Kontext abhängig: es wird zum überschreiben des Typen benutzt (XXX ptr YYY), zum deklarieren von typisierten Zeigern (typedef, proto/proc) oder es stellt den Datentypen eines Pointers dar (proto/proc, je nach segment word size 32Bit oder 16 Bit)

    MASM hat eine Besonderheit gegenüber andern Assemblern: Wenn man auf eine Variable zugreifen will, braucht man nur das label hinzuschreiben:

    mov eax,nr ;   gewöhnlicher MASM-Syntax, der zu bevorzugen ist.
    

    Die eckigen Klammern kann man, wie du es bereits getan hast, zusätzlich verwenden:

    mov eax,[nr]
    mov eax,DWORD ptr [nr]  ; ”DWORD ptr” würde den Datentypen überschreiben – ist hier aber nicht notwendig
    

    Wenn man die Adresse laden will, muss man den OFFSET-Operator oder LEA benutzen:

    mov eax,OFFSET nr
    lea eax,nr
    

    Im Zusammenhang mit INVOKE taucht dann auch noch der ADDR-Operator auf, welcher ebenfalls eine Adresse zurückgibt.

    Bezüglich Adressierung hat MASM einen extrem flexible Syntax – Obiges stellt nur ein kleine Ausschnitt der Möglichkeiten dar.

    Zur Puffergröße: Vielleicht nicht effizient, aber korrekt ist es, zuerst die Anzahl der Ziffern zu ermitteln, und dann den Puffer entsprechend zu füllen. Ändert aber nichts daran, dass der statische angelegte Puffer für die Maximale Größe ausgelegt sein muss.
    Die Konsole kannst du mit dem inkey-macro offenhalten (macros.asm oder masm32rt.inc).

    ---> MASM Programmer's Guide <---



  • Danke, für die weitere Antwort und den MASM Programmer Link direkt der erste Google Eintrag: http://www.masm32.com/board/index.php?topic=5433.0

    Dort findet man den Guide zum herunterladen, als chm Datei da ist alles übersichtlich dargestellt. Das Buch muss unbedint noch in die FAQ dort wird ja alles erklärt.


Anmelden zum Antworten