汇编程序在调用或退出时崩溃

时间:2019-01-27 23:45:45

标签: visual-studio assembly masm

在VS2015中调试我的代码,我结束了程序的结尾。但是,寄存器在call ExitProcess上应有的状态或其任何变型都会导致“访问冲突写入位置0x00000004”。我正在使用Kip Irvine的书中的Irvine32.inc。我尝试使用call DumpRegs,但这也会引发错误。

我尝试使用call ExitProcess的其他变体,例如exitinvoke 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 :(得分:2)

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],但指针递增通常更有效。)

这是高效(标量)实现的外观。 (使用SIMD的SSE2,您可以通过一条指令添加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:

请注意,进入循环之前先检查特殊情况 可以使我们简化它。另请参见Why are loops always compiled into "do...while" style (tail jump)?

sub eax, ecxcmp 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 CPU上,大多数情况下这实际上并不快;即使输入不相同,它们也可以并行运行inc,cmp和sub。这可能对AMD Bulldozer-整数核非常狭窄的家族。)

显然重复的减法是提高性能的总垃圾。可以实现更好的算法,例如一次一次长除法,但是{{1} }指令将对任何事物都更快,除非您知道商为0或1,因此最多需要减1。 ({idiv / div与任何其他整数运算相比都相当慢,但是专用硬件比循环要快得多。)

如果您确实需要手动执行有符号除法,通常会记录符号,取无符号绝对值,然后进行无符号除法。

例如idiv / xor eax, ecx如果EAX和ECX具有相同的符号,则为dl = 0;如果它们不同,则为1(因此商将为负)。 (根据结果的符号位设置SF,并且异或针对不同的输入产生1,对于相同的输入产生0。)