Page 1 of 1

Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 12:50 pm
by floppy121
в IRC-клиенте есть 2048-байтовый массив для хранения команд юзера:

Code: Select all

MAX_COMMAND_LEN = 512
user_command rb MAX_COMMAND_LEN*4
в дальнейшем я пытаюсь очистить хотя бы первые 255 байт для начала, при помощи новой функции:
(в главном файле объявлен счётчик цикла "counter db 0")

Code: Select all

clear_user_command:

    mov al, 0

    .cycle_clear:

    mov byte[counter], al
    mov byte[user_command + counter], 0 ; очищение байта в user_command
    add al, 1
    cmp al, 255
    jne .cycle_clear

    ; печать получившейся строки
    mov esi, user_command
    call print_asciiz

    ret
Казалось бы, этот простой и понятный цикл действительно должен заменить первые 255 байт у user_command нулями,
но после вызова этой функции ( call clear_user_command ) при выводе на печать я по-прежнему вижу неочищенное начало старой команды юзера.
Что я делаю не так?

Re: Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 2:03 pm
by tsdima
floppy121 wrote:Что я делаю не так?
Всё не так :)
Если вопрос - почему не работает, то причина в команде

Code: Select all

    mov byte[user_command + counter], 0 ; очищение байта в user_command
Здесь user_command и counter это адреса переменных. Ты их складываешь (это делается на этапе ассемблирования), получая некий адрес, и записываешь туда байт 00. Всегда. По одному и тому-же адресу.

Что нужно сделать, чтобы заработал конкретно этот код. Ну, во-первых нужно использовать регистр eax, т.к. к адресу нужно прибавлять 32-битное значение. А во-вторых, вышеупомянутую команду нужно записать немного иначе. К тому-же никакой переменной counter не нужно, её роль будет играть регистр eax.

Code: Select all

clear_user_command:

    mov eax, 0

    .cycle_clear:

    mov byte[user_command][eax], 0 ; очищение байта в user_command
    add eax, 1
    cmp eax, 255
    jne .cycle_clear

    ; печать получившейся строки
    mov esi, user_command
    call print_asciiz

    ret
Запись в память можно делать:
- по фиксированному адресу mov byte[user_command], 0
- по адресу в регистре mov byte[eax], 0
- или по сумме фиксированного значения и регистра mov byte[user_command][eax], 0

Команду mov eax, 0 обычно заменяют на более короткую (в коде) xor eax,eax
Команду add eax, 1 лучше заменить на более короткую inc eax

Но вообще, для заполнения области памяти имеется команда rep stosb (или rep stosd, которая быстрее, и записывает по 4 байта за цикл).

Re: Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 2:42 pm
by floppy121
tsdima wrote:- или по сумме фиксированного значения и регистра mov byte[user_command][eax], 0
К сожалению именно этот, самый нужный пример, не собирается:

Code: Select all

flat assembler  version 1.71.54
userparser.inc [501]:
    mov byte[user_command][eax], 0
error: invalid operand
При этом остальные два примера собираются нормально. Не могу понять в чём дело...

Re: Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 3:25 pm
by tsdima
floppy121 wrote:К сожалению именно этот, самый нужный пример, не собирается
Забыл, что fasm не любит такую запись. Попробуй mov byte[user_command+eax], 0

Re: Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 4:06 pm
by floppy121
tsdima wrote:
floppy121 wrote:К сожалению именно этот, самый нужный пример, не собирается
Забыл, что fasm не любит такую запись. Попробуй mov byte[user_command+eax], 0
Благодарю, сработало! :P вот что в итоге получилось:
ircc.asm

Code: Select all

MAX_COMMAND_LEN         = 512
MAX_COMMAND_LEN_BYTES   = MAX_COMMAND_LEN*4
userparser.inc

Code: Select all

clear_user_command:

    xor eax, eax

  .cycle_clear:

    mov byte[user_command + eax], 0
    inc eax
    cmp eax, MAX_COMMAND_LEN_BYTES
    jne .cycle_clear

    ret

Re: Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 6:44 pm
by 0CodErr
Можно ещё добавить варианты, используемые в других программах.

Для заполнения области памяти байтами с определённым значением(в том числе нулевым) обычно используется memset.
Вот эта функция в Tiny C Compiler http://websvn.kolibrios.org/filedetails ... memset.asm

Code: Select all

format ELF
section '.text' executable
public memset
memset:
  push  edi
  mov   edi,[esp+8]
  mov   eax,[esp+12]
  mov   ecx,[esp+16]
  jecxz  .no_set
  cld
  rep   stosb
.no_set:
  mov eax, [esp+8]
  pop   edi
  ret
А вот она же в программе graph http://websvn.kolibrios.org/filedetails ... memset.asm

Code: Select all

format MS COFF
section '.text' code readable executable
public _memset
_memset:
        push    edi
        mov     edi, [esp+8]
        mov     al, [esp+12]
        mov     ecx, [esp+16]
        rep     stosb
        pop     edi
        ret
Я же в своей libc использую такой вариант

Code: Select all

GLOBAL _memset

SECTION .text

;**********************************************************************************
_memset: ;//////////////////////////////////////////////////////////////////////////
;**********************************************************************************
%define ptr    [esp +  4 +1*4] ; Pointer to block of memory to fill
%define value  [esp +  8 +1*4] ; Value to be set
%define num    [esp + 12 +1*4] ; Number of bytes to set to value
DEBUG.PRINT.LINE "memset start"
        push   edi
        mov    eax, value
        mov    edx, num   
        mov    edi, ptr   
        mov    ecx, eax
        mov    ah, al
        mov    ch, cl
        shl    ecx, 16
        or     eax, ecx
        mov    ecx, edx
        shr    ecx, 2
        and    edx, 3
        rep stosd
        mov    ecx, edx
        rep stosb        
        mov    eax, ptr        
        pop    edi
DEBUG.PRINT.LINE "memset finish"
        ret
%undef ptr
%undef value
%undef num
        
Вот функция FillChar из RTL Delphi7

Code: Select all

{     ->EAX     Pointer to destination  }
{       EDX     count   }
{       CL      value   }
        PUSH    EDI
        MOV     EDI,EAX { Point EDI to destination              }
        MOV     CH,CL   { Fill EAX with value repeated 4 times  }
        MOV     EAX,ECX
        SHL     EAX,16
        MOV     AX,CX
        MOV     ECX,EDX
        SAR     ECX,2
        JS      @@exit
        REP     STOSD   { Fill count DIV 4 dwords       }
        MOV     ECX,EDX
        AND     ECX,3
        REP     STOSB   { Fill count MOD 4 bytes        }
@@exit:
        POP     EDI
Вот memset от микрософта(CRT Microsoft Visual C++ 6.0)

Code: Select all

public  memset
memset proc
        mov     edx,[esp + 0ch] ; edx = "count"
        mov     ecx,[esp + 4]   ; ecx points to "dst"
        test    edx,edx         ; 0?
        jz      short toend     ; if so, nothing to do
        xor     eax,eax
        mov     al,[esp + 8]    ; the byte "value" to be stored
; Align address on dword boundary
        push    edi             ; preserve edi
        mov     edi,ecx         ; edi = dest pointer
        cmp     edx,4           ; if it's less then 4 bytes
        jb      tail            ; tail needs edi and edx to be initialized
        neg     ecx
        and     ecx,3           ; ecx = # bytes before dword boundary
        jz      short dwords    ; jump if address already aligned
        sub     edx,ecx         ; edx = adjusted count (for later)
adjust_loop:
        mov     [edi],al
        inc     edi
        dec     ecx
        jnz     adjust_loop
dwords:
; set all 4 bytes of eax to [value]
        mov     ecx,eax         ; ecx=0/0/0/value
        shl     eax,8           ; eax=0/0/value/0
        add     eax,ecx         ; eax=0/0val/val
        mov     ecx,eax         ; ecx=0/0/val/val
        shl     eax,10h         ; eax=val/val/0/0
        add     eax,ecx         ; eax = all 4 bytes = [value]
; Set dword-sized blocks
        mov     ecx,edx         ; move original count to ecx
        and     edx,3           ; prepare in edx byte count (for tail loop)
        shr     ecx,2           ; adjust ecx to be dword count
        jz      tail            ; jump if it was less then 4 bytes
        rep     stosd
main_loop_tail:
        test    edx,edx         ; if there is no tail bytes,
        jz      finish          ; we finish, and it's time to leave
; Set remaining bytes
tail:
        mov     [edi],al        ; set remaining bytes
        inc     edi
        dec     edx             ; if there is some more bytes
        jnz     tail            ; continue to fill them
; Done
finish:
        mov     eax,[esp + 8]   ; return dest pointer
        pop     edi             ; restore edi
        ret
toend:
        mov     eax,[esp + 4]   ; return dest pointer
        ret
memset  endp
Если кто-то интересовался проектом Ziron http://codeziron.com/index.php то там в memutils используется memFill — слегка изменённый вариант функции для моей libc(см. выше)

Code: Select all

//******************************************************//
//                                                      //
//     Ziron Run Time Library                           //
//     Ziron Memory Utils (memutils.zir)                //
//                                                      //
//     Copyright (c) 2013-####, OverHertz OOD           //
//                                                      //
//******************************************************//

#ifdef BITS == 32:

#set frame off;

/*
/    Fill block of memory with specified value
/    $buf -- Pointer to block of memory to fill
/    $len -- Number of bytes to set to value
/    $ch_ -- Value to be set
*/
inline function m_memFill($buf, $len, $_ch) {
	// Copyright (c) 0CodErr, KolibriOS team
	using(edi) {
		eax = 0;
		al  = $_ch;
		edx = $len;
		edi = $buf;
		push edi
		ecx = eax;
		ah  = al;
		ch  = cl;
		ecx << 16;
		eax |= ecx;
		ecx = edx;
		ecx >> 2;
		edx &= 3;
		rep stosd;
		ecx = edx;
		rep stosb;
		pop eax
	}
	
	$return eax;
}

function memFill(char* buf; dword len; char ch_) {
	eax = m_memFill(buf, len, ch_);
}

Re: Как правильно очистить массив байтов?

Posted: Wed Jun 06, 2018 11:42 pm
by GerdtR
В простейшем случае, если надо очистить первые 256 байтов( или другое кратное 4 число):
xor eax,eax ; так обнулил eax. Нужно всё зачистить не нулями, а другим число(напр. 0х13) - пишем mov eax,0x13131313
mov edi,my_mem ;та память, которую очистим
mov ecx, 256/4 ; - тут количество байтов, делённо на 4 - для скорости очищаем сразу по 4 байта
rep stosd
Если уж очень надо очистить именно 255, то или меняем stosd на stosb( ну и в ecx уже пишем 255, без всякого деления), или усложняем код: зачищаем первые 252 байта с помощью stosd, потом дочищаем stosb. На практике так делать стоит, если у нас десяток килобайт чистится.