Программа сборки вылетает при вызове или выходе - PullRequest
0 голосов
/ 28 января 2019

Отлаживая мой код в VS2015, я дошел до конца программы.Регистры в том виде, в каком они должны быть, однако, call ExitProcess, или любой другой вариант, вызывает «Место записи нарушения доступа 0x00000004».Я использую Irvine32.inc из книги Кипа Ирвина.Я пытался использовать call DumpRegs, но это тоже выдает ошибку.

Я пытался использовать другие варианты call ExitProcess, такие как exit и invoke ExitProcess,0, которые тоже не работали, выбрасывая то же самоеошибка.Раньше, когда я использовал тот же формат, код работал нормально.Единственное различие между этим кодом и последним - использование регистров общего назначения.

include Irvine32.inc

.data
        ;ary        dword   100, -30, 25, 14, 35, -92, 82, 134, 193, 99, 0
        ary     dword   -24, 1, -5, 30, 35, 81, 94, 143, 0

.code
main PROC
                                ;ESI will be used for the array
                                ;EDI will be used for the array value
                                ;ESP will be used for the array counting
                                ;EAX will be used for the accumulating sum
                                ;EBX will be used for the average
                                ;ECX will be used for the remainder of avg
                                ;EBP will be used for calculating remaining sum
        mov     eax,0           ;Set EAX register to 0
        mov     ebx,0           ;Set EBX register to 0
        mov     esp,0           ;Set ESP register to 0
        mov     esi,OFFSET ary  ;Set ESI register to array
sum:    mov     edi,[esi]       ;Set value to array value
        cmp     edi,0           ;Check value to temination value 0
        je      finsum          ;If equal, jump to finsum
        add     esp,1           ;Add 1 to array count
        add     eax,edi         ;Add value to sum
        add     esi,4           ;Increment to next address in array
        jmp     sum             ;Loop back to sum array
finsum: mov     ebp,eax         ;Set remaining sum to the sum
        cmp     ebp,0           ;Compare rem sum to 0
        je      finavg          ;Jump to finavg if sum is 0
        cmp     ebp,esp         ;Check sum to array count
        jl      finavg          ;Jump to finavg if sum is less than array count
avg:    add     ebx,1           ;Add to average
        sub     ebp,esp         ;Subtract array count from rem sum
        cmp     ebp,esp         ;Compare rem sum to array count
        jge     avg             ;Jump to avg if rem sum is >= to ary count
finavg: mov     ecx,ebp         ;Set rem sum to remainder of avg

        call ExitProcess
main ENDP
END MAIN

Регистры до call ExitProcess

EAX = 00000163 EBX = 0000002C ECX = 00000003 EDX = 00401055
ESI = 004068C0 EDI = 00000000 EIP = 0040366B ESP = 00000008
EBP = 00000003 EFL = 00000293 

OV = 0 UP = 0 EI = 1 PL = 1 ZR = 0 AC = 1 PE = 0 CY = 1 

1 Ответ

0 голосов
/ 28 января 2019

mov esp,0 устанавливает указатель стека на 0. Любые инструкции стека, такие как push / pop или call / ret, вылетят после того, как вы это сделаете.

Выберите другой регистр для временного подсчета массива, а не указатель стека! У вас есть еще 7 вариантов выбора, похоже, что EDX по-прежнему не используется.

В обычном соглашении о вызовах только EAX, ECX и EDX блокируются вызовом (так что вы можетеиспользовать их без сохранения значения вызывающего абонента).Но вы звоните ExitProcess вместо возврата из main, чтобы вы могли уничтожить все регистры.Но ESP должен быть действителен, когда вы call.

call работают, помещая адрес возврата в стек, как sub esp,4 / mov [esp], next_instruction / jmp ExitProcess.См. https://www.felixcloutier.com/x86/CALL.html. Как показывает ваш дамп регистра, ESP = 8 перед call, поэтому он пытается сохранить по абсолютному адресу 4.


Ваш код имеет 2разделы: цикл по массиву и затем нахождение среднего. Вы можете повторно использовать регистр для разных вещей в 2 разделах , что часто значительно снижает нагрузку на регистр.(т. е. у вас не заканчиваются регистры.)

Использование массивов неявной длины (оканчивающихся элементом стража, таким как 0), необычно вне строк.Гораздо более распространенной является передача функции указатель + длина вместо простого указателя.

Но в любом случае, у вас есть массив неявной длины, поэтому вы должны найти его длину и запомнить это при вычислении среднего значения.Вместо того, чтобы увеличивать счетчик размера внутри цикла, вы можете вычислить его по указателю, который вы также увеличиваете.(Или используйте счетчик в качестве индекса массива, например ary[ecx*4], но приращение указателя часто более эффективно.)

Вот как может выглядеть эффективная (скалярная) реализация .(С SSE2 для SIMD вы можете добавить 4 элемента с одной инструкцией ...)

Используется всего 3 регистра .Я мог бы использовать ECX вместо ESI (чтобы main мог ret, не уничтожив ни одного из регистров, которые, как ожидал вызывающий объект, он сохранит, только EAX, ECX и EDX), но я сохранил ESI для совместимости с вашей версией.

.data
        ;ary        dword   100, -30, 25, 14, 35, -92, 82, 134, 193, 99, 0
        ary     dword   -24, 1, -5, 30, 35, 81, 94, 143, 0

.code
main PROC
;; inputs: static ary of signed dword integers
;; outputs: EAX = array average, EDX = remainder of sum/size
;;          ESI = array count (in elements)
;; clobbers: none (other than the outputs)

                                ; EAX = sum accumulator
                                ; ESI = array pointer
                                ; EDX = array element temporary

        xor     eax, eax        ; sum = 0
        mov     esi, OFFSET ary ; incrementing a pointer is usually efficient, vs. ary[ecx*4] inside a loop or something.  So this is good.
sumloop:                       ; do {
        mov     edx, [esi]
        add     edx, 4
        add     eax, edx        ; sum += *p++  without checking for 0, because + 0 is a no-op

        test    edx, edx        ; sets FLAGS the same as cmp edx,0
        jnz     sumloop         ; }while(array element != 0);
;;; fall through if the element is 0.
;;; esi points to one past the terminator, i.e. two past the last real element we want to count for the average

        sub     esi, OFFSET ary + 4  ; (end+4) - (start+4) = array size in bytes
        shr     esi, 2          ; esi = array length = (end-start)/element_size

        cdq                     ; sign-extend sum into EDX:EAX as an input for idiv
        idiv     esi            ; EAX = sum/length   EDX = sum%length

        call ExitProcess
main ENDP

Я использовал инструкцию аппаратного деления x86 вместо цикла вычитания.Ваш цикл повторного вычитания выглядел довольно сложным, но ручное деление со знаком может быть сложным. Я не понимаю, где вы обрабатываете вероятность того, что сумма будет отрицательной. Если ваш массив имел отрицательную сумму, повторное вычитание заставило бы его расти до тех пор, пока оно не переполнилось.Или в вашем случае вы выходите из цикла, если sum < count, что будет истинно на первой итерации для отрицательной суммы.

Обратите внимание, что комментарии типа Set EAX register to 0 бесполезны.Мы уже знаем это из чтения mov eax,0.sum = 0 описывает смысл семантики , а не архитектурный эффект.Есть некоторые хитрые инструкции для x86, в которых есть смысл прокомментировать то, что он даже делает в этом конкретном случае, но mov не является одним из них.

Если вы просто хотели сделать повторное вычитание с помощьюПредположим, что sum неотрицателен для начала, это так просто:

;; UNSIGNED division  (or signed with non-negative dividend and positive divisor)
; Inputs: sum(dividend) in EAX,  count(divisor) in ECX
; Outputs: quotient in EDX, remainder in EAX  (reverse of the DIV instruction)
    xor    edx, edx                 ; quotient counter = 0
    cmp    eax, ecx
    jb     subloop_end              ; the quotient = 0 case
repeat_subtraction:                 ; do {
    inc    edx                      ;   quotient++
    sub    eax, ecx                 ;   dividend -= divisor
    cmp    eax, ecx
    jae    repeat_subtraction       ; while( dividend >= divisor );
     ; fall through when eax < ecx (unsigned), leaving EAX = remainder
subloop_end:

Обратите внимание, как проверка особых случаев перед входом в цикл позволяет упростить его.См. Также Почему циклы всегда компилируются в стиле "do ... while" (прыжок с хвоста)?

sub eax, ecx и cmp eax, ecx в одном цикле кажется избыточным: мы могли бы простоиспользуйте sub для установки флагов и исправьте перерегулирование.

    xor    edx, edx                 ; quotient counter = 0
    cmp    eax, ecx
    jb     division_done            ; the quotient = 0 case
repeat_subtraction:                 ; do {
    inc    edx                      ;   quotient++
    sub    eax, ecx                 ;   dividend -= divisor
    jnc    repeat_subtraction       ; while( dividend -= divisor doesn't wrap (carry) );

    add    eax, ecx                 ; correct for the overshoot
    dec    edx
division_done:

(Но на самом деле это не так быстро в большинстве случаев на большинстве современных процессоров x86; они могут запускать inc, cmp и sub параллельнодаже если входные данные не совпадают. Это может помочь в семействе AMD Bulldozer, где целочисленные ядра довольно узкие.)

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

Если вам нужно реализовать деление со знаком вручную, обычно вызапишите знаки, примите абсолютное значение без знака, затем выполните деление без знака.

Например, xor eax, ecx / sets dl дает вам dl = 0, если EAX и ECX имели одинаковый знак, или 1, если онибыли разные (и, следовательно, частное будет отрицательным).(SF устанавливается в соответствии со знаковым битом результата, и XOR выдает 1 для разных входов, 0 для одинаковых входов.)

...