Warum wird nur ein Zeichen ausgebenen?



  • Guten Morgen,

    ich bin weiter fleißig am ausprobieren (ich glaube ich müsste echt mal schauen ob ich da was passendes finde, aber bisher bin ich für x64 Assembler-Programmierung nicht wirklich fündig geworden) und habe versucht ein Zeichen, bzw. nun einen String auszugeben.

    Ich gehe so vor, dass ich erst ein Zeichen auf den Stack schiebe und anschließend die Adresse mittels `lea` an write() übergebe. Das klappt mit einem Zeichen auch wunderbar, jedoch nicht wenn ich zwei Elemente auf den Stack schiebe und den size-Parameter eine 2 übergebe.

    .global _start
    
    .text
    _start:
        pushq $65 
        pushq $66 
    
        movq $1, %rax
        movq $1, %rdi
        leaq (%rsp), %rsi
        movq $2, %rdx
        syscall
    
        movq $60, %rax
        xor %rdi, %rdi
        syscall
    

    push dekrementiert laut der Dokumentation von AMD den Stackpointer und schiebt den Wert auf den Stack. Also müsste der Stack nach den beiden push doch so aussehen:

    65
    66 <-- und hier müsste %sp hinzeigen
    

    Oder sehe ich das falsch? Warum klappt das nicht so, wie ich mir das vorstelle.
    Meine Idee war auch schon dem leaq-Befehl einen Offset von 1 anzugeben, weil ich mir über die Reihenfolge nicht ganz sicher war, aber wenn ich es mit

    leaq 0x1(%rsp), %rsi
    

    versuche, wird gar nichts mehr ausgegeben.

    Vielen Dank!



  • Der size-Parameter (EDX) wird in Bytes angegeben und sys_write gibt hintereinanderliegende Bytes aus. PUSHQ pusht 8 Bytes auf den Stack ('q' steht für Quadword = 4 * Word = 8 * Bytes). $65 (dezimal) wird vom Assembler umgewandelt in 0x0000000000000041 (hex) und im Speicher abgelegt als 41,00,00,00,00,00,00,00. Das nächste PUSHQ speichert eine solche Folge davor. Letztendlich sieht der Stack so aus:

    00
    00
    00
    00
    00
    00
    00
    41
    00
    00
    00
    00
    00
    00
    00
    42 <-- hier zeigt %rsp hin
    

    Wenn Du dem size-Parameter eine '$9' spendierst, erwischt Du noch die 41. Das ist aber nicht besonders sauber, weil theoretisch auch die Nullen ausgegeben werden, bzw. diese ab und zu eine besondere Bedeutung haben. Wenn Du den Stack bündig füllen willst, musst Du eine große Zahl pushen (einzelne Bytes geht nicht) und zweckmäßigerweise auf hexadezimal umstellen. Ersetze mal Deine PUSH durch

    movq $0x323575647544, %rax
    pushq %rax
    

    und stelle Deinen size_Parameter auf $6 ein. Den Umweg über RAX habe ich genommen, weil mein GAS sich weigert, den PUSH eines 6-Byte-Wertes zu assemblieren.

    viele grüße
    ralph



  • Achh.... Wie dämlich bin ich denn? 😉 Ich hätte die man-page vernünftig lesen sollen. Klar, wenn nur Byteweise geschrieben wird, ist das natürlich nachvollziehbar.

    Ich wollte mich ganz auf 64-Bit-Entwicklung konzentrieren und versuche ausschließlich die 64-Bit-Anweisungen und Register zu verwenden, aber wäre es hier nicht sogar sinnvoll push zu verwenden? (Ich suche gerade wie viel Byte das pusht, da pushb scheinbar entfernt wurde.)

    Ist es eigentlich sinnvoll, je nach Größe lieber %sp, %esp oder %rsp zu verwenden, oder sollte ich das lieber nicht mischen?

    Vielen Dank für deine Hilfe!



  • Dudu52 schrieb:

    Ich wollte mich ganz auf 64-Bit-Entwicklung konzentrieren und versuche ausschließlich die 64-Bit-Anweisungen und Register zu verwenden, aber wäre es hier nicht sogar sinnvoll push zu verwenden? (Ich suche gerade wie viel Byte das pusht, da pushb scheinbar entfernt wurde.)

    Wenn Du das Suffix weglässt, versucht GAS aus anderen Informationen die Größe zu bestimmen. Ein 'mov $65, %rax' assembliert 'movq', ein 'mov $65, %al' assembliert 'movb'. Wenn GAS die Größe nicht bestimmen kann, meckert es. Bei PUSH geht GAS aber von 64 bit aus und pusht 8 Bytes.

    Zum Spielen:

    .global main
    
    .data
    fmt: .asciz "%016llX\n"
    
    .text
    main:
    
        mov %rsp, %rsi       ; Ausgangswert von RSP ausgeben
        mov $fmt, %edi
        xor %eax, %eax
        call printf
    
        push $65             ; Immediate pushen 
        mov %rsp, %rsi       ; Neuen Wert von RSP ausgeben
        mov $fmt, %edi
        xor %eax, %eax
        call printf
    
        mov $12345678, %eax
        push %ax             ; 16-bit-Register pushen
        mov %rsp, %rsi       ; Neuen Wert von RSP ausgeben
        mov $fmt, %edi
        xor %eax, %eax
        call printf
    
        xor %rdi, %rdi
        call exit
    

    Assemblieren mit GCC (Erweiterung der Assembler-Datei: .s) und ausführen mit './a.out'.

    Ist es eigentlich sinnvoll, je nach Größe lieber %sp, %esp oder %rsp zu verwenden, oder sollte ich das lieber nicht mischen?

    Stack-Pointer nicht mischen! Wenn Du ESP einen Wert zuweist, werden die oberen 32 bit von RSP gelöscht (eine Eigenheit des 64-bit-Modus). Bei Berechnungen mit SP gehen Überträge verloren. Bleibe bei RSP.

    viele grüße
    ralph

    EDIT: Ich gehe mal davon aus, dass Du auf Linux programmierst, auf Windows (MinGW, CygWin) funktioniert mein Programm nicht, bzw. müsste ganz anders programmiert werden. Dasselbe vermute ich für MacOSX und BSD.



  • Ah super, vielen Dank für deine Antwort! Ich schaue mir den Code mal an!

    Jup, ich entwickle auf meinem 64 Bit Debian-System. 🙂


Anmelden zum Antworten