Как использовать строки в emu8086

Мне нужна помощь со строками в emu8086. Я инициализировал строку:

str1 db "0neWord"

И у меня есть пустая строка:

str2 db ?

Теперь мне нужно проверить все буквы в str1 и скопировать в str2, но если буква в str1 равна 0, мне нужно заменить ее на О. Если нет, мне нужно просто скопировать букву.

Как я могу это сделать?


person Victoria Sanchez    schedule 13.11.2016    source источник


Ответы (2)


str2 db ? не пустая строка. db означает "определить байт", а ? означает один неинициализированный байт.

db "0neWord" удобен для ассемблера, он скомпилируется в серию байтов, определенных как '0', 'n', 'e', ..., 'd'. В ассемблере нет такого понятия, как "строковый" тип, все скомпилировано в машинный код, который можно рассматривать как последовательность байтов. Какой «тип» данных хранится в памяти, зависит от инструкций, используемых для доступа к ним, но в памяти все представляет собой просто серию байтов и может рассматриваться как таковая.

Возможно, вам пора проверить документацию по отладчику emu8086 и посмотреть память по адресу str1 после загрузки кода в отладчик, чтобы увидеть, как он скомпилировался.

Поэтому, как только вы скопируете второй байт из str1 в str2, вы начнете перезаписывать часть памяти, которую вы не ожидали перезаписать.

Чтобы выделить некоторый буфер памяти фиксированного размера, вы можете использовать, например, str2 db 100 DUP(?), выполнив 100 раз ? определение для db, тем самым зарезервировав там 100 байтов памяти, следующие байты машинного кода в том же разделе будут скомпилированы за пределами адреса str2+100.


Чтобы что-то сделать с str1 "строкой", вам нужно знать:

1) его адрес в памяти, у ассемблера x86 есть много способов получить его, но два самых простых:

  • mov <r16>,OFFSET str1 (r16 — любой регистр 16b)
  • lea <r16>,[str1] (в данном случае делает то же самое)

2) его размер ИЛИ структура. Вы не поместили туда никакой структуры, например, строки с завершающим нулем имеют байт со значением 0 в конце, или служба DOS int 21h, ah=9 для отображения строки ожидает строку, заканчивающуюся знаком доллара '$' и т. д. Таким образом, вам нужен как минимум размер. И директиву ассемблера EQU, и "текущую позицию" можно использовать для вычисления размера str1 следующим образом:

str1 db "0neWord"
str1size EQU $-str1  ; "$" is assemblers "current_address" counter

Хм, сначала я попытался проверить это, прочитав некоторые документы, но мне очень трудно найти хорошую полную документацию по emu8086 (нашел что-то вроде "справки", и в ней полностью отсутствует описание директив ассемблера).

Интересно, почему так много людей все еще останавливаются на этом, а не на linux + nasm/подобном, которые полностью бесплатны, с открытым исходным кодом и задокументированы.

Итак, будем надеяться, что emu8086 работает как MASM/TASM, и что я все еще правильно помню этот синтаксис, тогда указанное выше определение размера должно работать. В противном случае обратитесь к своим примерам/документам.


Наконец, когда у вас есть адрес, размер и достаточно большой целевой буфер (опять же, чтобы загрузить его адрес, вы можете использовать OFFSET или lea в emu8086), вы можете закодировать свою задачу, например, таким образом:

    ; pseudo code follows, replace it by actual x86 instructions
    ; and registers as you wish
    ; ("r16_something" means one of 16b register, r8 is 8b register)
    lea   r16_str1,[str1]   ; load CPU with address of str1
    mov   r16_counter,str1size  ; load CPU with str1 size value
    lea   r16_str2,[str2]   ; load address of target buffer
loop_per_character:
    mov   r8_char,[r16_str1] ; read single character
    cmp   r8_char,'0'
    jne   skip_non_ascii_zero_char
    ; the character is equal to ASCII '0' character (value 48)
    mov   r8_char,'O'   ; replace it with 'O'
skip_non_ascii_zero_char:
    ; here the character was modified as needed, write it to str2 buffer
    mov   [r16_str2],r8_char
    ; make both str1/2 pointers to point to next character
    inc   r16_str1
    inc   r16_str2
    ; count down the counter, and loop until zero is reached
    dec   r16_counter
    jnz   loop_per_character
    ; the memory starting at "str2" should now contain
    ; modified copy of "str1"

    ; ... add exit instructions ...

Хм.. оказывается, "псевдокод" - это полный код x86, вам просто нужно назначить реальные регистры псевдо-и заменить их везде в исходнике.

Я постарался разместить там очень обширные комментарии (с моей точки зрения), чтобы можно было понять каждую используемую инструкцию. Вы должны проконсультироваться с каждым из справочного руководства по инструкциям Intel, перечитывая его с любым учебным пособием / уроками, которые у вас есть для сборки, пока вы не почувствуете, что понимаете, что такое регистр, память и т. д.

Также отлаживайте код по инструкции, проверяя состояние ЦП (значения регистров, флаги) и содержимое памяти после каждой инструкции, чтобы понять, как это работает.

person Ped7g    schedule 14.11.2016

Есть несколько способов сделать это. Вот некоторые примеры:

1) Со строковой инструкцией:

.model small

.data 

        str1 db "0neWord$"

        size equ $-str1

        str2 db size dup ('') 


.code  

main:

        mov ax, @data
        mov ds, ax 

        mov cx, size

        cld          ; DF might have been set, but we want lodsb to go forwards
        lea si, str1
        mov ax, 0  
        mov bx, 0

     copyStr:

        lodsb  ;str1 to al

        cmp al, '0'
        je alterChar        

        mov str2[bx], al
        jmp continue

     alterChar:

        mov str2[bx], 'o'

     continue:

        inc bx

        loop copyStr 

        mov str2[bx], '$'

        mov ah, 09h 
        lea dx, str2
        int 21h     

        mov ah, 04ch
        int 21h                  

end main 

2) Без строковой инструкции:

.model small

.data 

        str1 db "0neWord$"
        str2 db ?

.code  

main:

        mov ax, @data
        mov ds, ax

        mov si, 0               

        call copyStr      

        mov ah, 09h
        lea dx, str2
        int 21h

        mov ah, 04ch
        int 21h

   copyStr proc 

        mov bx, 0

      compute:            

           mov bl, str1 [si]

           cmp bl, '0'
           je alterChar 

           mov str2[si], bl

           jmp continue           

        alterChar:

           mov str2 [si], 'o'    

        continue:

           inc si

           cmp str1[si], '$'
           je return                      
           jmp compute

      return:

           mov str2[si], '$' 
           ret     

   copyStr endp    

end main 

Инструкция LODSB вы можете узнать больше о строковых инструкциях из здесь

3) С lodsb/stosb и упрощенным/оптимизированным:

.model small
.data 
        str1 db "0neWord$"
        size equ $-str1

        str2 db size dup ('') 

.code  
main:
        mov   ax, @data
        mov   ds, ax
        mov   es, ax       ; stosb stores to [es:di]

        mov   si, OFFSET str1
        mov   di, OFFSET str2
        cld          ; make sure stosb/lodsb go forwards

     ; copy SI to DI, including the terminating '$'
     copyStr:           ; do {
        lodsb             ; str1 to al

        cmp   al, '0'
        je    alterChar        

     doneAlteration:
        stosb             ; al to str2
        cmp   al, '$'
        jne   copyStr    ; } while(c != '$')

        mov   ah, 09h      ; print implicit-length string
        mov   dx, OFFSET str2
        int   21h     

        mov   ah, 04ch     ; exit
        int   21h                  

     alterChar:
        mov   al, 'o'
        ;jmp   doneAlteration
        stosb
        jmp   copyStr

end main 
person Ahtisham    schedule 16.11.2017
comment
Вы можете использовать stosb, чтобы сделать это намного более компактным и упростить ветвление, чтобы для входов, отличных от '0', не было принятой ветви. (т. е. перейти к блоку, который устанавливает al, и прыгнуть обратно в цикл, вместо того, чтобы заставлять неиспользованную сторону также прыгать.) - person Peter Cordes; 16.11.2017
comment
Кроме того, lea dx, str2 на один байт длиннее, чем mov dx, OFFSET str2, и не имеет никаких преимуществ. - person Peter Cordes; 16.11.2017
comment
Я внес изменения, чтобы добавить более эффективную версию с изменениями, которые я предложил в комментариях. (не проверено). Вместо использования инструкции loop я разветвился на завершающей $. Это быстрее на современных процессорах, но, возможно, медленнее на реальном 8086, потому что loop более компактен (меньше кода внутри цикла, хотя снаружи требуется больше настроек). Моя версия, вероятно, все еще быстрее на реальном 8086, особенно если большинство символов не '0'. - person Peter Cordes; 16.11.2017
comment
И кстати, я мог бы опубликовать свою версию как отдельный ответ, но этот вопрос на самом деле не нуждался в еще одном ответе, но неэффективность вашего кода беспокоила меня, поэтому я решил улучшить ваш. Не стесняйтесь редактировать, как вам нравится. - person Peter Cordes; 16.11.2017
comment
@PeterCordes хорошо, ваш код печатает символы мусора. - person Ahtisham; 16.11.2017
comment
Спасибо за тест, что я сделал не так? Разве str2 не завершается должным образом после копирования? Согласно spike.scu.edu.au/~barry/interrupts.html# ah09, ah=9 / int 21h должен вывести строку, заканчивающуюся $, начиная с ds:dx. О, нам нужен cld, чтобы очистить DF? Я привык к 32/64-битному коду, где обычные соглашения о вызовах требуют DF=0 при входе/выходе функции. - person Peter Cordes; 16.11.2017
comment
изменился с этого mov ah, 09h mov dx, OFFSET str2 int 21h на этот mov ah, 09h mov dx, es:[di] int 21h, это устранило проблему :) - person Ahtisham; 16.11.2017
comment
Хм, это не имеет смысла, вы загружаете первые 2 байта строки и передаете их как указатель. Но упоминание es напоминает мне, что я забыл инициализировать es = ds, а stosb пишет в [es:di] - person Peter Cordes; 16.11.2017
comment
@PeterCordes да, stosb пишет в es, вот где я запутался, когда увидел ваш код. - person Ahtisham; 16.11.2017
comment
Вы действительно тестировали с mov dx, es:[di]? Если да, то мне интересно, как это работает. Можете ли вы сделать это пошагово? - person Peter Cordes; 16.11.2017
comment
@PeterCordes на самом деле печатает сам ввод. es:[di] печатает входной массив, а не тот, который хранится в es. - person Ahtisham; 16.11.2017
comment
es:[di] печатает 0neWord вместо oneWord тот, где 0 не o. - person Ahtisham; 16.11.2017
comment
Я запустил ваш код, потому что мне было любопытно stosb, я только что узнал, что он сохраняет результат в es, но ваш код полностью отрицал это. :) и кстати, когда я писал свой код, я думал о stosb, но не придавал этому особого значения. иначе я бы написал упрощенный, как у вас;) - person Ahtisham; 16.11.2017
comment
Ваш mov dx, es:[di] сработал только по стечению обстоятельств. Он устанавливает dx = 0, а ds:0 — это место, где хранится str1. (Память в es:[di] еще не была записана, потому что она за концом места записи stosb. emu8086 начинается с инициализации нулями). В emu8086 с шаблоном exe ds и es начинаются как 0x0700, а @data — это 0x0710. str1 находится в начале сегмента данных, поэтому mov si, OFFSET str1 собирается в mov si, 0. Вот почему позднее обнуление dx путем загрузки из нетронутой памяти до того, как ah=9h / int 21h напечатает str1. - person Peter Cordes; 16.11.2017
comment
И да, довольно здорово изучать новые инструкции и выяснять, для чего они нужны. :) Есть еще несколько инструкций по AVX512. Я не совсем понял цель тем не менее (и, возможно, некоторые из них, о которых я даже забыл, существуют; есть масса новых инструкций). Так что я полностью понимаю, как здорово найти хорошее применение для инструкции, о которой вы только что узнали. - person Peter Cordes; 16.11.2017
comment
@ Претер Кордес вау! 512 бит? я сплю ? лол, я не могу полностью выучить 16-битную инструкцию. как я могу узнать 512-битный один. Этому было бы очень трудно научиться. - person Ahtisham; 16.11.2017
comment
Они делают то же самое для всех элементов параллельно. Это только усложняется, когда элементы не просто a[i .. i+15], и вы перетасовываете, чтобы выровнять одни элементы с некоторыми другими элементами. Для цикла по массиву, выполняющему c[i] = a[i] + b[i], это не сложнее, чем с SSE2 с 4 упакованными элементами в векторе 128b. Если вам интересно, см. deplinenoise.wordpress.com/2015/03/06/ за хорошее введение в SIMD. - person Peter Cordes; 16.11.2017