Hex2Bin



  • Meine ersten zarten Versuche mit Assembler und ich dachte mir schreibe ich doch mal einen Hex2Bin-Converter. Er funktioniert auch. Da es aber meine ersten Versuche sind, wäre es nett wenn mal erfahrene Leute drüber schauen. Über jegliche Verbesserungsvorschläge bin ich natürlich dankbar, sowohl was die Programmlogik als auch den Coding-Style und die Verwendung der Register angeht. Das kleine Programm ist geschrieben im Emulator emu8086 und benutzt seinen Syntax und seine Interrupts.

    ; You may customize this and other start-up templates; 
    ; The location of this template is c:\emu8086\inc\0_com_template.txt
    
    org 100h
    
    start:
        mov ax, 0abcdh      ; value to be bit-printed
        mov di, ax          ; move it to di
        mov cl, 0fh         ; initialize the shiftcounter to 15
                            ; we are dealing with 16 bit wide values
    nextbit:
        mov ax, 01h         ; ax hold the bitmask
        shl ax, cl          ; shift the mask in place for the current bit
        and ax, di          ; mask out all other bits than the one we're looking at
        cmp ax, 00h         ; is the resulting value still greater than zero?
        ja printOne         ; it is so print a '1'
        mov al, '0'         ; it's not so there was a zero in place
        call printchar      ; print the '0'
        jmp continue        ; jump over the next block
    printOne:
        mov al, '1'         ; put the '1'
        call printchar      ; and print it
    continue:
        cmp cl, 00h         ; is the shiftcounter smaller than zero?
        je done             ; it is so it's done
        dec cl              ; it's not so decrease it
        jmp nextbit         ; and go for the next bit
    done:    
        int 20h             ; go home
    
    ; expects:
    ;   al - char to be printed
    printchar:
        mov ah, 0eh         ; single char print func-#: 0x0e
        int 10h             ; vid interrupt
        ret                 ; go back
    


  • Du koenntest die Bitmaske gleichzeitig als Zaehlvariable benutzen:

    mov bx, 0abcdh
          mov cx, 08000h    ; beim obersten bit anfagen
    nextbit:
          test bx, cx
          setz al
          add  al,'0'
          call printchar
          shr cx,1
          jnz nextbit
    


  • alternativer Vorschlag:

    mov bx, 0abcdh
        mov cx, 0010h
    
    nextbit:
        mov ax, 0e00h
        shl bx, 1
        adc al, 30h
        int 10h        ; printchar
        dec cx
        jnz nextbit
    

    Dein Code ist fuer einen Anfaenger nicht schlecht. Beim x86 ist vor allem zu beachten, dass eigentlich jede logisch/arithmetische Rechenoperation die Flags dem Ergebnis entsprechend setzt. So kannst du in deinem Code eigentlich schon mal ohne Einschraenkungen alle cmp-Befehle einfach weglassen (evtl. statt dem ja noch ein je schreiben).
    Bei einer for-Schleife, deren Abbruchbedingung ein Vergleich mit 0 ist, kann man sich einen expliziten Vergleichsbefehl, wie in unseren beiden Beispielen zu sehen, zB. oft sparen. dec dekrementiert cx und setzt zugleich die flags. Ist das Ergebnis 0, auch das z-Flag (jz ist btw. aequivalent je).
    Falls es sich nicht vermeiden laesst, mit 0 zu vergleichen, macht man idR. statt einem "cmp reg, 0" auch eher "test reg, reg". Ist kuerzer, sollte aber das gleiche Ergebnis bringen.

    Funktionsaufrufe per call fressen uebrigens Platz auf dem Stack (Ruecksprungaddresse) und Rechenzeit. Daher ist es nicht ratsam, einfach wie in Hochsprachen leider oft ueblich, Prozeduren allein zur uebersichtlichen Gliederung des codes zu missbrauchen, sondern nur einzusetzen, wenn es aus irgendwelchen Gruenden wirklich sinnvoll ist. Ansonsten bieten viele Assembler auch flexible macros, oder falls die Funktion eh nur aus 2 Zeilen in einem kleinen Code besteht, schreib' den Code einfach direkt rein und einen Kommentar dahinter.

    BTW:
    Hast du zwar nicht gemacht, aber gern (zT. sogar von "Profis") arglos heruntergecodete Sachen wie

    mov ah, 0ch
    mov al, 00h
    

    sind natuerlich eine schreckliche Suende. 😃
    1. verbraet der Zugriff auf Teilregister (hier al und ah als Teil von ax) beim x86 mitunter zusaetzlich Zeit.
    2. koennen 2 direkt aufeinanderfolgende Zugriffe auf dasselbe Register die Pipeline potentiell durcheinander bringen.
    3. ist ein "mov ax, 0c00h" nicht nur schneller, sondern auch noch kuerzer.



  • Danke für eure beiden Vorschläge. Viel eleganter und effizienter als mein Versuch. Mir fehlt da noch der Überblick, welche Befehle welche Flags setzen und welche nicht. Und welche Befehle es überhaupt gibt. 🙂 Sehr verwirrend alles. Dagegen ist C ein Joke mit seinem kleinen Sprachumfang. Und daher auch mein erster Versuch, so wie er ist. So würde ich es in C programmieren. In Assembler denkt man aber wohl noch anders. Noch komprimierter und logischer.

    Mir ist aufgefallen, daß ihr beide den zu untersuchenden Wert in BX packt, gibt es einen Grund dafür oder ist das Wurscht? Ich hab ihn im Gegensatz zu euch ja nach DI gepackt. Ich frage weil CX ja irgendwie als "Counter"-Register benutzt wird. Also jegliche Counter Funktionialität sollte, was ich bisher gehört habe, im CX Register stattfinden.

    Und last but not least. Ich benutz Windows-XP und wie ich mich informiert habe, kann ich unter Windows leider nicht mehr auf die Interrupts zugreifen, wie ich es noch zu DOS-Zeiten konnte. Gibt es da einen Workaround unter Windows? Dann könnte ich mich von diesem Emulator lösen, der btw z.B. "setz" gar nicht kennt.



  • NDEBUG schrieb:

    Dagegen ist C ein Joke mit seinem kleinen Sprachumfang.

    Naja, C hat dafuer andere ueble Huerden. 😉

    NDEBUG schrieb:

    Mir ist aufgefallen, daß ihr beide den zu untersuchenden Wert in BX packt, gibt es einen Grund dafür oder ist das Wurscht? Ich hab ihn im Gegensatz zu euch ja nach DI gepackt. Ich frage weil CX ja irgendwie als "Counter"-Register benutzt wird. Also jegliche Counter Funktionialität sollte, was ich bisher gehört habe, im CX Register stattfinden.

    Ich habe nicht nachgeschaut, ob int 10h, ah=0eh irgendwas in bx erwartet. Wahrscheinlich nicht - dann ist es wirklich egal. Ich habe da einfach nur bei hellihjb abgekupfert. 😃
    cx bietet sich hauptsaechlich vom Namen her als counter an. Ansonsten kannst du ausser bei Spezialbefehlen wie loop oder rep, die von sich aus cx zum Zaehlen benutzen, eigentlich jedes allg. Register benutzen.

    NDEBUG schrieb:

    Ich benutz Windows-XP und wie ich mich informiert habe, kann ich unter Windows leider nicht mehr auf die Interrupts zugreifen, wie ich es noch zu DOS-Zeiten konnte. Gibt es da einen Workaround unter Windows? Dann könnte ich mich von diesem Emulator lösen, der btw z.B. "setz" gar nicht kennt.

    Jo, ist richtig: In Windows kannst du die BIOS-Interrupts nicht mehr benutzen. Der einzige "Workaround" waere, DOS-Programme zu schreiben und entweder auf einem eigenen Rechner mit DOS oder in einem Emulator wie DOSBox zu starten.
    Windows-Programme benutzen ansonsten idR. die Win-API (und laufen im 32Bit Protected Mode im Gegensatz zum 16Bit Real Mode bei DOS).



  • Mir ist aufgefallen, daß ihr beide den zu untersuchenden Wert in BX packt

    Das lag lediglich daran, dass ich Deine printchar-Funktion intakt lassen wollte und dadurch ax als Parameter "belegt" war.

    Mir fehlt da noch der Überblick, welche Befehle welche Flags setzen und welche nicht. Und welche Befehle es überhaupt gibt.

    Steht zB hier.

    Dann könnte ich mich von diesem Emulator lösen, der btw z.B. "setz" gar nicht kennt.

    Eine Moeglichkeit waere zB, Deinen Assembler-Code in C einzubetten (was auch diverse Tuecken birgt):

    // __fastcall veranlasst, dass der Parameter in (e)cx erwartet wird
    void __fastcall printchar(char c)
    {
       printf("%c", c);
    }
    
    void hex2bin(unsigned short v)
    {
       _asm {
          mov ax, v
          mov dx, 0x8000
    
    bitloop:
          test ax, dx
          setz cl
          add  cl, '0'
          pusha
          call printchar
          popa
          shr  dx,1
          jnz  bitloop
       }
    }
    
    int main()
    {
       hex2bin(0xabcd);
       return 0;
    }
    


  • Das mit dem Einbetten nach C sieht auf jeden Fall sehr gut aus. Ich werd mal nachher genauer danach schauen, ob ich da Doks zu finde. Daher danke für den Tip. Später soll es sowieso so laufen, daß der Assembler Code als Startupcode für ein C-Programm dient und einige Funktionionalität nach Assembler ausgelagert werden soll. Dann werde ich aber auch wieder direkt auf die Interrupts zugreifen können.

    Es ist nur für den Assembler Beginner ziemlich doof, wenn die Interrupts gesperrt sind, da ich ja doch hier und da mal gerne sehen will, was mein Assembler Programm im Moment gerade tut.


Anmelden zum Antworten