汇编开发(三):程序

作者: _凌浩雨 | 来源:发表于2019-02-16 23:19 被阅读1次

    1. 堆栈操作

    1). 运行时栈
    • PUSH 操作

    作用:32位PUSH操作将堆栈指针递减4并将值复制到该位置堆栈指针指向的堆栈

    PUSH操作.png
    • POP 操作

    作用:POP操作从栈中移除一个数据, 数据移除之后, 栈指针增加指向更高的堆栈位置

    POP操作.png
    2). PUSH 和 POP 指令
    • PUSH 指令

    PUSH指令将ESP地址减小并且源操作数到栈中

    PUSH reg/mem16
    PUSH reg/mem32
    PUSH imm32
    
    • POP 指令

    POP指令复制ESP指针指向的数据到目的操作数中,并增加ESP的数值

    POP reg/mem16
    POP reg/mem32
    
    • PUSHFD 和 POPFD 指令

    PUSHFD 指令将32位EFLAGS寄存器压入栈中,POPFD将32位EFLAGS移出栈中

    pushfd
    popfd
    
    • PUSHAD, PUSHA, POPAD, and POPA 指令

    PUSHAD / POPAD 将32位通用寄存器(EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI)压入/移出栈中
    PUSHA / POPA 将16位寄存器(AX, CX, DX, BX, SP, BP, SI, DI)压入 / 移出栈中

    MySub PROC
    pushad ; save general-purpose registers
    .
    .
    mov eax,...
    mov edx,...
    mov ecx,...
    .
    .
    popad ; restore general-purpose registers
    ret
    MySub ENDP
    
    3). 查看数组地址

    方法:调试 -> 窗口 -> 内存 -> 内存1,在地址栏中输入(&+变量名)即可查看数组

    内存.png
    4). 字符串翻转
    .486        ; 定义32位程序可以接受32位的寄存器和地址
    .model flat, stdcall    ; 选择程序的内存模式为平坦模式,stdcall调用习惯
    .stack 4096             ; 设置运行的堆栈大小为4096字节
    ExitProcess PROTO, dwExitCode: DWORD    
    
    
    COMMENT &
        字符串翻转
    &
    .data
        aName BYTE "Abraham Lincoln", 0
        nameSize = ($ - aName) - 1
    
    .code
    main PROC                   ; 定义主函数开始位置
        ; 将那么长度存入寄存器
        mov ecx, nameSize
        mov esi, 0
    L1: movzx eax, aName[esi]   ; 获取字符
        push eax                ; 压入栈中
        inc esi                 ; 循环变量自加
        LOOP L1
    
        ; 翻转字符串
        mov ecx, nameSize
        mov esi, 0
    L2: pop eax                 ; 获取字符
        mov aName[esi], al      ; 存放字符
        inc esi                 ; 循环变量自加
        LOOP L2
    
        INVOKE ExitProcess, 0   ; 退出程序2
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    END main            ; 设置了函数的入口与出口
    

    2. 定义和使用程序

    1). PROC 指令
    • 定义一个程序
    main PROC
    .
    .
    main ENDP
    

    非main方法定义如下:

    sample PROC
    .
    .
     ret
    sample ENDP
    

    其中RET强制CPU返回方法被Call的位置。

    • 程序中的标签
    jmp Destination
    Destination::
    
    2). CALL 和 RET 指令
    • CALL
      处理器直接调用程序开始执行在一个新的内存位置

    • RET
      程序指针返回被调用的位置

    3). 嵌套过程调用

    循环嵌套调用应该当被调用的过程在第一个之前调用另一个过程时
    程序返回

    循环嵌套调用.png

    假设main函数调用一段名为Sub1的程序,当Sub1正在执行的时候,它调用名为Sub2的程序,当Sub2正在执行的时候,它调用名为Sub3的程序。

    4). 参数传递

    注:通过寄存器传递参数

    示例:

    .486        ; 定义32位程序可以接受32位的寄存器和地址
    .model flat, stdcall    ; 选择程序的内存模式为平坦模式,stdcall调用习惯
    .stack 4096             ; 设置运行的堆栈大小为4096字节
    ExitProcess PROTO, dwExitCode: DWORD    
    
    COMMENT &
        求和
    &
    .data
        theSum DWORD ?
    
    .code
    ;------------------------------------------------------
    ; SumOf
    ; 计算并返回是三个数之和
    ; Receieves: EAX, EBX, ECX, 三个数. 
    ; Returns: EAX = sum
    ;------------------------------------------------------
    SumOf PROC
        add eax, ebx            ; 计算EAX与EBX之和
        add eax, ecx            ; 计算EAX与ECX之和
        RET                     ; 返回程序调用的位置
    SumOf ENDP
    
    main PROC                   ; 定义主函数开始位置
        mov eax, 10000h         ; 参数1
        mov ebx, 20000h         ; 参数2
        mov ecx, 30000h         ; 参数3
    
        call SumOf              ; 调用SumOf方法,并传递参数
    
        mov theSum, eax         ; 将计算结果赋值给theSum变量
    
        INVOKE ExitProcess, 0   ; 退出程序2
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    END main            ; 设置了函数的入口与出口
    
    5). 数组求和之程序调用
    .486        ; 定义32位程序可以接受32位的寄存器和地址
    .model flat, stdcall    ; 选择程序的内存模式为平坦模式,stdcall调用习惯
    .stack 4096             ; 设置运行的堆栈大小为4096字节
    ExitProcess PROTO, dwExitCode: DWORD    
    
    COMMENT &
        计算数组之和
    &
    .data
        array DWORD 10000h, 20000h, 30000h, 40000h, 50000h
        theSum DWORD ?
    
    .code
    main PROC                   ; 定义主函数开始位置
        mov esi, OFFSET array   ; ESI 指向数组
        mov ecx, LENGTHOF array ; ECX 存放数组长度
        call ArraySum           ; 调用数组求和程序
        mov theSum, eax         ; 赋值
    
        INVOKE ExitProcess, 0   ; 退出程序2
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    
    ;------------------------------------------------------
    ; ArraySum
    ; 计算数组元素之和
    ; Receieves: ESI = 数组偏移地址
    ;            ECS = 数组长度
    ; Returns: EAX = 各数组元素之和
    ;------------------------------------------------------
    ArraySum PROC
        push esi            ; 保存ESI
        push ecx            ; 保存ECX
        mov eax, 0          ; 设置数组之和为0
    L1:                     ; 循环头
        add eax, [esi]      ; 将当前数组指针指向的数组元素值加入EAX寄存器
        add esi, TYPE DWORD ; 移动数组指针到下一个元素
        LOOP L1             ; 设置循环
    
        pop ecx             ; 恢复ecx值
        pop esi             ; 恢复esi值
        ret                 ; 返回程序调用位置
    ArraySum ENDP
    
    END main            ; 设置了函数的入口与出口
    
    6). 保存和恢复寄存器

    问题:每一段中都需要不PUSH和POP寄存值,这样在书写程序时会出现代码冗余的现象,并且如果忘记,则程序数据会出现异常。丢失数据。

    • USES 操作数
      伴随着PROC指令出现,后面跟着程序要修改的所有寄存器的名字列表。
    ;------------------------------------------------------
    ; ArraySum
    ; 计算数组元素之和
    ; Receieves: ESI = 数组偏移地址
    ;            ECS = 数组长度
    ; Returns: EAX = 各数组元素之和
    ;------------------------------------------------------
    ArraySum PROC USES esi ecx
        mov eax,0           ; 设置和为0
    L1:
        add eax,[esi]       ; 添加每一项到EAX
        add esi,TYPE DWORD  ; 移动指针
        loop L1             ; 循环加
        ret                 ; 返回到程序调用的位置
    ArraySum ENDP
    

    3. 链接外部库

    1). 背景资料
    • 使用
    WriteString proto
    
    call WriteString
    
    • 链接命令行参数
      link hello.obj irvine32.lib kernel32.lib
    链接32位库.png

    4. Irvine32 库

    1). 配置Irvine32库
    2). Irvine32 库内容

    专业程序员通常更喜欢建立自己的库,这样做是一种很好的教育体验。 在Windows下运行的32位模式下,输入输出库必须直接调用操作系统。

    Procedure Description
    CloseFile Closes a disk file that was previously opened.
    Clrscr Clears the console window and locates the cursor at the upper left corner.
    CreateOutputFile Creates a new disk file for writing in output mode.
    Crlf Writes an end-of-line sequence to the console window.
    Delay Pauses the program execution for a specified n-millisecond interval.
    DumpMem Writes a block of memory to the console window in hexadecimal.
    DumpRegs Displays the EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EFLAGS, and EIP registers in hexadecimal. Also displays the most common CPU status flags.
    GetCommandTail Copies the program’s command-line arguments (called the command tail) into an array of bytes.
    GetDateTime Gets the current date and time from the system.
    GetMaxXY Gets the number of columns and rows in the console window’s buffer.
    GetMseconds Returns the number of milliseconds elapsed since midnight.
    GetTextColor Returns the active foreground and background text colors in the console window.
    Gotoxy Locates the cursor at a specific row and column in the console window.
    IsDigit Sets the Zero flag if the AL register contains the ASCII code for a decimal digit (0–9).
    MsgBox Displays a popup message box.
    MsgBoxAsk Display a yes/no question in a popup message box.
    OpenInputFile Opens an existing disk file for input.
    ParseDecimal32 Converts an unsigned decimal integer string to 32-bit binary.
    ParseInteger32 Converts a signed decimal integer string to 32-bit binary.
    Random32 Generates a 32-bit pseudorandom integer in the range 0 to FFFFFFFFh.
    Randomize Seeds the random number generator with a unique value.
    RandomRange Generates a pseudorandom integer within a specified range.
    ReadChar Waits for a single character to be typed at the keyboard and returns the character.
    ReadDec Reads an unsigned 32-bit decimal integer from the keyboard, terminated by the Enter key.
    ReadFromFile Reads an input disk file into a buffer.
    ReadHex Reads a 32-bit hexadecimal integer from the keyboard, terminated by the Enter key.
    ReadInt Reads a 32-bit signed decimal integer from the keyboard, terminated by the Enter key.
    ReadKey Reads a character from the keyboard’s input buffer without waiting for input.
    ReadString Reads a string from the keyboard, terminated by the Enter key.
    SetTextColor Sets the foreground and background colors of all subsequent text output to the console.
    Str_compare Compares two strings.
    Str_copy Copies a source string to a destination string.
    Str_length Returns the length of a string in EAX.
    Str_trim Removes unwanted characters from a string.
    Str_ucase Converts a string to uppercase letters.
    WaitMsg Displays a message and waits for a key to be pressed.
    WriteBin Writes an unsigned 32-bit integer to the console window in ASCII binary format.
    WriteBinB Writes a binary integer to the console window in byte, word, or doubleword format.
    WriteChar Writes a single character to the console window.
    WriteDec Writes an unsigned 32-bit integer to the console window in decimal format.
    WriteHex Writes a 32-bit integer to the console window in hexadecimal format.
    WriteHexB Writes a byte, word, or doubleword integer to the console window in hexadecimal format.
    WriteInt Writes a signed 32-bit integer to the console window in decimal format.
    WriteStackFrame Writes the current procedure’s stack frame to the console.
    WriteStackFrameName Writes the current procedure’s name and stack frame to the console.
    WriteString Writes a null-terminated string to the console window.
    WriteToFile Writes a buffer to an output file.
    WriteWindowsMsg Displays a string containing the most recent error generated by MS-Windows.
    3). 概览
    • Console Window
      控制台窗口(或命令窗口)是在显示命令提示符时由MS-Windows创建的纯文本窗口。
    4). 个别程序说明
    • CloseFile: 关闭之前创建/打开的文件呢, 通过接收一个32位整型句柄。如果文件成功关闭,则EAX将返回非0值。
    mov eax,fileHandle
    call CloseFile
    
    • Clrscr: 清除控制台窗口,在程序的开始或结尾调用。其他时间如果要调用,需要调用WaitMsg暂停程序,然后再调用此程序。
    call WaitMsg ;   "Press any key to continue..."
    call Clrscr
    
    • CreateOutputFile: 在磁盘上创建一个新的文件,并以写的方式打开。当我们调用这个程序的时候,为EDX寄存器设置一个文件名。当程序返回时,如果创建成功,EAX包含一个有效的文件句柄;否则EAX等于INVALID_HANDLE_VALUE
    .data
    filename BYTE "newfile.txt",0
    .code
    mov edx,OFFSET filename
    call CreateOutputFile
    
    • Crlf: 控制台换行,它输出一个包含ASCII码'0Dh'和'0Ah'的字符串
    call Crlf
    
    • Delay: 暂停程序。当调用的时候,设置期望值到EAX中,当为毫秒(ms).
    mov eax,1000 ; 1 second
    call Delay
    
    • DumpMem: 以十六进制的形式将一系列内存写入控制台窗口。并将通过ESI赋值首地址,ECX赋值数组长度,EBX赋值数组元素所占字节数
    .data
        array DWORD 1,2,3,4,5,6,7,8,9,0Ah,0Bh
    .code
    main PROC
        mov esi,OFFSET array        ; 首地址
        mov ecx,LENGTHOF array      ; 数组长度
        mov ebx,TYPE array          ; 数组元素所占字节
        call DumpMem
    
    • DumpRegs: 以16进制形式显示EAX, EBX, ECX, EDX, ESI, EDI, EBP,ESP, EIP, and EFL (EFLAGS) 寄存器数值。它也可以显示CF,SF,ZF,OF,AF,PF标志位的数值。
    call DumpRegs
    
    • GetCommandTail: 将程序的命令行复制到以null结尾的字符串。如果命令行是空的,则设置CF标志位;否则,CF标志位清空。这段程序用在命令行参数获取上。当我们调用该程序时,EDX必须包含至少129个字节的偏移。
    .data
        cmdTail BYTE 129 DUP(0)     ; empty buffer
    .code
        mov edx,OFFSET cmdTail
        call GetCommandTail         ; fills the buffer
    

    设置命名行参数方法:工程名右键 -> 属性 -> 调试 -> 命名参数

    命名行参数设置.png
    • GetMaxXY: 获取控制台缓冲字节。如果控制台窗口缓冲字节大于可视化窗口大小,滚动条自动显示。该程序没有输入参数,返回时,DX寄存器包含缓冲列长度,AX寄存器包含缓冲行长度,它们的值不会大于255。
    .data
        rows BYTE ?
        cols BYTE ?
    .code
        call GetMaxXY
        mov rows,al
        mov cols,dl
    
    • GetMseconds: 从主机电脑获取当前的系统时间,并将时间返回给EAX寄存器。这段程序用于计算事件之间的执行时间。没有输入参数。
    .data
        startTime DWORD ?
    .code
        call GetMseconds    
        mov startTime,eax
    L1:
        ; (loop body)
        loop L1
        call GetMseconds
        sub eax,startTime ; EAX = loop time, in milliseconds
    
    • GetTextColor: 获取当前控制台的前景与背景色。没有输入参数,返回当前背景色并赋值给AL的高4位,前景色赋值给AL的低4位。
    .data
        color byte ?
    .code
        call GetTextColor
        mov color,AL
    
    • Gotoxy: 将控制台中的光标移动到指定位置(行与列)。默认的,控制台的x可选值为0—79,y可选值为0—24.当我们调用Gotoxy时,将Y(row)的值赋给DH,X(column)的值赋给DL。
    mov dh,10 ; row 10
    mov dl,20 ; column 20
    call Gotoxy ; locate cursor
    

    注:用户可能重设控制台窗口的大小,所以应当应用调用GetMaxXY获取rows和columns的取值。

    • IsDigit: 判断AL中的ASCII码是否为一个有效的十进制数字。当调用这段程序时,将ASCII码赋值给AL,如果AL中的内容是一个有效的十进制数据,那么设置零标记位;否则,清空零标记位。
    mov AL,somechar
    call IsDigit
    
    • MsgBox: 显示一个带着文本的图形弹出消息窗口(控制台空口模式下)。为EDX设置字符串,用于显示在窗体内部。可选的,在EBX中设置窗体的标题。如果没有标题,则设置EBX为0
    .data
        caption BYTE "Dialog Title", 0
        HelloMsg BYTE "This is a pop-up message box.", 0dh,0ah BYTE "Click OK to continue...", 0
    .code
        mov ebx,OFFSET caption
        mov edx,OFFSET HelloMsg
        call MsgBox
    
    • MsgBoxAsk: 显示一个带着Yes/No的图形弹出消息框(控制台模式下)。消息框内容,为EDX设置字符串。可选,EBX设置消息框标题,如果没有标题则设置EBX为0.该程序在EAX中返回一个整型,以告诉你用户的选择。IDYES为6,IDNO为7.
    .data
        caption BYTE "Survey Completed",0
        question BYTE "Thank you for completing the survey." 
                 BYTE 0dh,0ah
                 BYTE "Would you like to receive the results?",0
    .code
        mov ebx,OFFSET caption
        mov edx,OFFSET question
        call MsgBoxAsk
        ;(check return value in EAX)
    
    • OpenInputFile: 为输入打开一个已经存在的文件。其中EDX设置文件名,返回时,如果文件打开成功,EAX包含一个有效的文件句柄;否则,EAX等于INVALID_HANDLE_VALUE
    .data
        filename BYTE "myfile.txt",0
    .code
        mov edx,OFFSET filename
        call OpenInputFile
    
    • ParseDecimal32: 转换无符号十进制整数字符串到32位二进制。其中EDX设置字符串的首地址,ECX设置字符串的长度,返回时将二进制的值设置给EAX。
    .data
        buffer BYTE "8193"
        bufSize = ($ - buffer)
    .code
        mov edx,OFFSET buffer
        mov ecx,bufSize
        call ParseDecimal32 ; returns EAX
    

    -- 如果这个整型是空白的, 则EAX = 0 and CF = 1
    -- 如果这个整型仅仅包含空格, 则EAX = 0 and CF = 1
    -- 如果这个整型是大于2^32�1, 则EAX = 0 and CF = 1
    -- 否则, EAX包含转换后的整型并且CF = 0

    • ParseInteger32: 转换一个有符号的十进制整型字符串为32位二进制数据。所有有效的数字从字符串第一个非数字字符开始转换。字符串之前的空格忽略。EDX设置字符串的首地址,ECX设置字符串的长度,返回的数值存放在EAX中。
    .data
        buffer byte "-8193"
        bufSize = ($ - buffer)
    .code
        mov edx,OFFSET buffer
        mov ecx,bufSize
        call ParseInteger32 ; returns EAX
    
    • Random32: 在EAX寄存器中生成一个32位随机整型。重复调用时,Random32生成模拟随机序列。使用seed方法创建一个数字,这段程序使用种子生成一个随机数。
    .data
        randVal DWORD ? 
    .code
        call Random32
        mov randVal,eax 
    
    • Randomize: 初始化Random32的起始种子值和RandomRange程序。种子等于一天中的时间,精确到1/100秒。 每次运行一个调用Random32和RandomRange的程序的时间,生成的序列随机数将是唯一的。
        call Randomize
        mov ecx,10
    L1: call Random32
        ; use or display random value in EAX here...
        loop L1
    
    • RandomRange: 从0—(n-1)生成一个随机数,其中n赋值给EAX寄存器。随机数返回给EAX寄存器。
    .data
        randVal DWORD ?
    .code
        mov eax,5000
        call RandomRange
        mov randVal,eax
    
    • ReadChar: 从键盘读取一个字符,并返回这个字符到AL寄存器中。
    .data
        char BYTE ?
    .code
        ReadChar
        mov char, al
    

    如果用户按下一个扩展键,例如功能键Ins,Del,程序将设置AL为0,AH设置对应的键值。

    • ReadDec: 从键盘读取一个32位无符号整型,并将值设置到EAX中,忽略开头的空格。返回值是从第一个有效的数字开始直到一个非数字。
    .data
        intVal DWORD ?
    .code
        call ReadDec
        mov intVal, eax
    

    -- 如果这个整型是空白的, 则EAX = 0 and CF = 1
    -- 如果这个整型仅仅包含空格, 则EAX = 0 and CF = 1
    -- 如果这个整型是大于2^32�1, 则EAX = 0 and CF = 1
    -- 否则, EAX包含转换后的整型并且CF = 0

    • ReadFromFile: 读取文件中的内容到内存中。当你调用ReadFromFile时,EAX中存储已打开文件句柄,EDX中存储文件起始地址,ECX中存储文件内容最大字节数。ReadFromFile返回值,检查CF进位标志,如果CF是空的,EAX包含从文件中读取到的字节数,但如果CF被设置,EAX中包含一个系统的错误码。你可以调用WriteWindowsMsg程序获取异常文本。
    .data
        BUFFER_SIZE = 5000
        buffer BYTE BUFFER_SIZE DUP(?)
        bytesRead DWORD ?
    .code
        mov edx,OFFSET buffer       ; points to buffer
        mov ecx,BUFFER_SIZE         ; max bytes to read
        call ReadFromFile           ; read the file
        ; 如果CF标志位是空的,则可以执行这行代码
        ; mov bytesRead, eax        ; count of bytes actually read
        ; 如果CF标志位被设置,则调用WriteWindowsMsg
        ; call WriteWindowsMsg
    
    • ReadHex: 从键盘读取一个32位的十六进制数据,并将获取到的数据设置到EAX中。
    .data
        hexVal DWORD ?
    .code
        call ReadHex
        mov hexVal,eax
    
    • ReadInt: 从键盘读取一个32位的有符号整型,并将值设置到EAX中。开头位置用户可以使用+/-, 其余的数字仅包含数字。如果用户输入的值不是一个正确的32位有符号整数,那么该程序设置OF标志位显示一个异常信息。该程序返回所有有效的数字直到非数字字符为止。
    .data
        intVal SDWORD ? 
    .code
        call ReadInt
        mov intVal,eax
    
    • ReadKey: 执行无等待键盘检查。换句话说,检测用户是否按下了某个键。如果没有键盘数据,ZF标志位被设置;如果有键被按下,则ZF标志位被清空并设置AL寄存器为ASCII码值。如果AL等于0,则用户可能按下了一下特殊键,AH寄存器将包含虚拟的扫描码,DX包含一个虚拟键值,EBX包含键盘标识位。
    if no_keyboard_data then
        ZF = 1
    else
        ZF = 0
        if AL = 0 then
            extended key was pressed, and AH = scan code, DX = virtual key code, and EBX = keyboard flag bits
        else
            AL = the key's ASCII code
        endif
    endif
    
    • ReadString: 从键盘读取一个字符串,知道用户输入回车键为止。EDX设置字符串首地址,ECX设置用户最大输入字符数。返回时EAX中存放字符个数。
    .data
        buffer BYTE 21 DUP(0)   ; input buffer
        byteCount DWORD ?       ; holds counter
    .code
        mov edx,OFFSET buffer   ; point to the buffer
        mov ecx,SIZEOF buffer   ; specify max characters
        call ReadString         ; input the string
        mov byteCount,eax       ; number of characters
    
    • SetTextColor: 设置文本输出区域前景色和背景色。当调用SetTextColor时,分配一个颜色给EAX寄存器。下面是与定义的颜色常量,可以使用在前景色与背景色的设置。
    Color Color Color Color
    black = 0 red = 4 gray = 8 lightRed = 12
    blue = 1 magenta = 5 lightBlue = 9 lightMagenta = 13
    green = 2 brown = 6 lightGreen = 10 yellow = 14
    cyan = 3 lightGray = 7 lightCyan = 11 white = 15

    设置颜色时,EAX的高16位为背景色,低16位为前景色

    mov eax,white  (blue * 16)   ; white on blue
    call SetTextColor
    
    • Str_length: 返回一个控制终止的字符串长度。其中EDX设置字符串偏移量。返回时,EAX设置为字符串长度。
    .data
        buffer BYTE "abcde",0
        bufLength DWORD ?
    .code
        mov edx,OFFSET buffer   ; point to string
        call Str_length         ; EAX = 5
        mov bufLength,eax       ; save length
    
    • WaitMsg: 显示一个字符串"Press any key to continue..."并等待用户按下一个键。
    call WaitMsg
    
    • WriteBin: 以ASCII二进制格式将整数写入控制台窗口。其中EAX存放待输出的字符串。
        mov eax,12346AF9h
        call WriteBin
    
    • WriteBinB: 以ASCII二进制格式将32位整数写入控制台窗口,EAX寄存器中存放待输出的数值,EBX寄存器中存放要显示类型的字节大小。
        mov eax,00001234h
        mov ebx,TYPE WORD   ; 2 bytes
        call WriteBinB      ; displays 0001 0010 0011 0100
    
    • WriteChar: 在控制台窗口中输出一个字符,AL寄存器中存放该字符或ASCII码。
        mov al,'A'
        call WriteChar  ; displays: "A"
    
    • WriteDec: 在控制台窗口中输出一个开头没有0的32位无符号整数,其中EAX寄存器存放该数字。
        mov eax,295
        call WriteDec   ; displays: "295"
    
    • WriteHex: 在控制台窗口中输出一个8位十六进制的32位无符号整数,如果需要开始位置补0,EAX寄存器存放待输出的整数。
        mov eax,7FFFh
        call WriteHex   ; displays: "00007FFF"
    
    • WriteHexB: 在控制台窗口中输出一个指定十六进制格式的32位无符号整数,如果必须,开始位置补0。EAX寄存器存储待输出整数,EBX寄存器中存放待显示的位数。
        mov eax,7FFFh
        mov ebx,TYPE WORD   ; 2 bytes
        call WriteHexB      ; displays: "7FFF"
    
    • WriteInt: 在控制台窗口中输出一个带符号且不带0的32位十进制整数,EAX寄存器中存放待输出的整数。
        mov eax,216543
        call WriteInt   ; displays: "+216543"
    
    • WriteString: 在控制台窗口输出一个以空值终止的字符串,EDX寄存器中存放字符串首地址。
    .data
        prompt BYTE "Enter your name: ",0
    .code
        mov edx,OFFSET prompt
        call WriteString
    
    • WriteToFile: 将缓冲的内容输出到文件中,其中EAX寄存器中存放一个有效的文件句柄,EDX寄存器中存放缓冲内容的偏移地址,ECX寄存器中存放待写入的字节长度。当程序返回时,如果EAX寄存器大于0,则它的值为已经写入的字节长度;否则,为异常信息。
    BUFFER_SIZE = 5000
    .data
        fileHandle DWORD ?
        buffer BYTE BUFFER_SIZE DUP(?)
    .code
        mov eax,fileHandle
        mov edx,OFFSET buffer
        mov ecx,BUFFER_SIZE
        call WriteToFile
    
    • WriteWindowsMsg: 当执行系统函数时,你的应用在控制台窗口中输出一个最近生成的异常信息。
        call WriteWindowsMsg
    

    5. Irvine32 库测试程序

    1). 教程:Library Test #1
    • Step 1: 在程序开始位置添加标准头
    ; Library Test #1: Integer I/O
    ; 测试 Clrsrc, Crlf, DumpMem, ReadInt, SetTextColor
    ; WaitMsg, WriteBin, WriteHex, WriteString
    Include Irvine32.inc
    
    • Step 2: 定义常量COUNT用于决定程序循环次数,常量BlueTextOnGrayDefaultColor用于改变控制台背景与前景颜色。
    .data
        COUNT = 4               ; 设置循环次数
        BlueTextOnGray = blue + (lightGray * 16)            ; 控制台的前景色和背景色
        DefaultColor = lightGray + (black * 16)             ; 默认的控制台前景色和背景色
    
    • Step 3: 定义一个有符号的32位整型,使用十进制表示常量。定义一个字符串用于提示用于输入一个整数
    .data
        arrayD SDWORD 12345678h, 1A4B2000h, 3434h, 7AB9h    ; 数组
        prompt BYTE "Enter a 32-bit signed integer: ", 0    ; 提示信息
    
    • Step 4: 代码区域定义主程序,设置EAX值为BlueTextOnGray, 调用SetTextColor改变背景和前景色。为了使设置的颜色生效,必须调用清屏程序清屏。
    .code
    main PROC                   ; 定义主函数开始位置
        mov eax, BlueTextOnGray ; 设置控制台窗口颜色
        call SetTextColor       ; 设置颜色
        call Clrscr             ; 为了使设置的颜色生效,调用清屏方法
    
        INVOKE ExitProcess, 0   ; 退出程序
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    END main            ; 设置了函数的入口与出口
    
    • Step 5: 将ESI寄存器设置为arrayD数组的首地址
        mov esi, OFFSET arrayD  ; 设置数组首地址
    
    • Step 6: 将EBX设置为数组每个元素所占用的字节数。
        mov ebx, TYPE arrayD    ; 设置数组元素所占字节数
    
    • Step 7: 将ECX设置为数组长度,然后调用DumpMem显示。
        mov esi, OFFSET arrayD  ; 设置数组首地址
        mov ebx, TYPE arrayD    ; 设置数组元素所占字节数
        mov ecx, LENGTHOF arrayD; 数组元素个数
        call DumpMem            ; 显示信息
    

    效果如下:


    DumpMem效果.png
    • Step 8: 调用Crlf输出一个空行,然后初始化ECX的值为COUNT用于循环。
        call Crlf               ; 输出空行
        mov ecx, COUNT          ; 设置循环次数
    
    • Step 9: 显示一个字符串信息提示用户输入数字,字符串的首地址赋值给EDX,调用WriteString程序输出,然后调用ReadInt程序接受用户的输入,将这个值赋值给EAX。
    L1: 
        mov edx, OFFSET prompt  ; 设置字符串首地址
        call WriteString        ; 输出提示信息
        call ReadInt            ; 将输入的数字读入EAX
        call Crlf               ; 输出空行
    
    • Step 10: 调用WriteInt显示一个格式化的十进制有符号数字, 然后调用Crlf输出空行
        call WriteInt           ; 显示一个有符号的十进制数
        call Crlf               ; 输出空行
    
    • Step 11: 调用WriteHexWriteBin显示十六进制数和二进制数。
        call WriteHex           ; 显示十六进制数
        call Crlf               ; 输出空行
        call WriteBin           ; 显示二进制数
        call Crlf               ; 输出空行
        call Crlf               ; 输出空行
    
    • Step 12: 设置循环
        LOOP L1                 ; 设置循环,此时ECX递减
    
    • Step 13: 循环结束后,显示一个结束信息并暂停程序。
        call WaitMsg            ; 显示请按任意键继续信息
    
    • Step 14: 程序的结尾,将颜色设置回默认的
        mov eax, DefaultColor   ; 控制器默认的前景色与背景色
        call SetTextColor       ; 设置颜色
        call Clrscr             ; 清屏
    
    

    效果:

    InputLoop程序.png
    • Step 15: 完整程序
    ; Library Test #1: Integer I/O
    ; 测试 Clrsrc, Crlf, DumpMem, ReadInt, SetTextColor
    ; WaitMsg, WriteBin, WriteHex, WriteString
    Include Irvine32.inc
    
    .data
        COUNT = 4               ; 设置循环次数
        BlueTextOnGray = blue + (lightGray * 16)            ; 控制台的前景色和背景色
        DefaultColor = lightGray + (black * 16)             ; 默认的控制台前景色和背景色
        arrayD SDWORD 12345678h, 1A4B2000h, 3434h, 7AB9h    ; 数组
        prompt BYTE "Enter a 32-bit signed integer: ", 0    ; 提示信息
    
    .code
    main PROC                   ; 定义主函数开始位置
        ; 设置蓝色文本和亮灰色背景
        mov eax, BlueTextOnGray ; 设置控制台窗口颜色
        call SetTextColor       ; 设置颜色
        call Clrscr             ; 为了使设置的颜色生效,调用清屏方法
    
        ; 使用DumpMem显示数组
        mov esi, OFFSET arrayD  ; 设置数组首地址
        mov ebx, TYPE arrayD    ; 设置数组元素所占字节数
        mov ecx, LENGTHOF arrayD; 数组元素个数
        call DumpMem            ; 显示信息
        ; 让用户输入有符号整型
        call Crlf               ; 输出空行
        mov ecx, COUNT          ; 设置循环次数
    
    L1: 
        mov edx, OFFSET prompt  ; 设置字符串首地址
        call WriteString        ; 输出提示信息
        call ReadInt            ; 将输入的数字读入EAX
        call Crlf               ; 输出空行
    
        ; 以十进制,十六进制,二进制形式输出一个整数
        call WriteInt           ; 显示一个有符号的十进制数
        call Crlf               ; 输出空行
    
        call WriteHex           ; 显示十六进制数
        call Crlf               ; 输出空行
        call WriteBin           ; 显示二进制数
        call Crlf               ; 输出空行
        call Crlf               ; 输出空行
    
        LOOP L1                 ; 设置循环,此时ECX递减
    
        call WaitMsg            ; 显示请按任意键继续信息
    
        ; 设置控制台颜色为默认的
        mov eax, DefaultColor   ; 控制器默认的前景色与背景色
        call SetTextColor       ; 设置颜色
        call Clrscr             ; 清屏
    
        exit
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    END main            ; 设置了函数的入口与出口
    
    2). Library Test #2: 随机数
    ; Library Test #2
    ; Rendom Integers
    Include Irvine32.inc
    
    TAB = 9             ; Tab的ASCII码
        
    .code
    main PROC                   ; 定义主函数开始位置
        call Randomize          ; 初始化随机数生成器
        call Rand1              ; 调用Rand1程序
        call Rand2              ; 调用Rand2程序
    
        call WaitMsg            ; 输出等待按键信息
        exit
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    
    Rand1 PROC
        ; 生成10个伪随机数
        mov ecx, 10             ; 循环10次
    L1: 
        call Random32           ; 随机数生成初始化
        call WriteDec           ; 输出一个无符号十进制数
        mov al, TAB             ; 设置TAB键的ASCII码
        call WriteChar          ; 输出一个字符
        LOOP L1                 ; 设置循环
    
        call Crlf               ; 输处空行
        RET                     ; 回到调用该程序的位置
    Rand1 ENDP
    
    Rand2 PROC
        ; 从-50到49生成是个伪随机数
        mov ecx, 10             ; 设置循环次数
    L1:
        mov eax, 100            ; 设置值的范围为0-99
        call RandomRange        ; 生成随机数
        sub eax, 50             ; 减去50,使生成的值的范围为-50——49
        call WriteInt           ; 输出有符号的十进制
        mov al, TAB             ; 设置TAB的ASCII码
        call WriteChar          ; 输出TAB
        LOOP L1                 ; 设置循环
    
        call Crlf               ; 输出空行
        RET
    Rand2 ENDP
    END main            ; 设置了函数的入口与出口
    
    Random 效果.png
    3). Library Test #3 : 性能时间
    ; Library Test #3: Performance Timing
    ; 计算嵌套循环的执行时间
    Include Irvine32.inc
        
    .data
        OUTER_LOOP_COUNT = 3    ; 循环次数
        startTIme DWORD ?       ; 循环开始时间
        msg1 BYTE "Please wait...", 0Dh, 0Ah, 0 ;等待信息
        msg2 BYTE "Elapsed milliseconds: ", 0   ; 经过时间
    
    .code
    main PROC                   ; 定义主函数开始位置
        mov edx, OFFSET msg1    ; 设置msg1的偏移地址
        call WriteString        ; 输出msg1
        
        ; 保存开始时间
        call GetMSeconds        ; 获取当前时间
        mov startTime, eax      ; 保存开始时间
    
        ; 设置外层循环次数
        mov ecx, OUTER_LOOP_COUNT
    
    L1: 
        call innerLoop          ; 调用内层循环
        LOOP L1
    
        ; 计算执行时间
        call GetMSeconds        ; 获取当前时间
        sub eax, startTime      ; 计算出执行时间
    
        ; 显示执行时间
        mov edx, OFFSET msg2    ; 设置msg2的偏移地址
        call WriteString        ; 输出msg2
        call WriteDec           ; 输出循环执行时间
        call Crlf               ; 输出空行
    
        call WaitMsg            ; 输出等待按键信息
        exit
    main ENDP           ; 函数结束位置, ENDP 之前的内容,要与PROC 
    
    innerLoop PROC USES ecx
        mov ecx, 0FFFFFFFh      ; 设置循环次数
    L1: 
        mul eax                 ; 乘法
        mul eax             
        mul eax
        LOOP L1                 ; 重复内层循环
    
        RET
    innerLoop ENDP
    
    END main            ; 设置了函数的入口与出口
    
    Performance Timing.png

    6. 64位汇编程序

    1). Irvine64 库
    Procedure Description
    Crlf Writes an end-of-line sequence to the console.
    Random64 Generates a 64-bit pseudorandom integer in the range 0 to 2^64�1. The random value is returned in the RAX register.
    Randomize Seeds the random number generator with a unique value.
    ReadInt64 Reads a 64-bit signed integer from the keyboard, terminated by the Enter key. It returns the integer value in the RAX register.
    ReadString Reads a string from the keyboard, terminated by the Enter key. Pass it the offset of the input buffer in RDX, and set RCX to the maximum number of characters the user can enter, plus 1 (for the null terminator byte). It returns a count (in RAX) of the number of characters typed by the user.
    Str_compare Compares two strings. Pass it a pointer to the source string in RSI, and a pointer to the target string in RDI. Sets the Zero and Carry flags in the same way as the CMP (Compare) instruction.
    Str_copy Copies a source string to the location indicated by a target pointer. Pass the source offset in RSI, and the target offset in RDI.
    Str_length Returns the length of a null-terminated string in the RAX register. Pass it the string’s offset in RCX.
    WriteInt64 Displays the contents of the RAX register as a 64-bit signed decimal integer, with a leading plus or minus sign. It has no return value.
    WriteHex64 Displays the contents of the RAX register as a 64-bit hexadecimal integer. It has no return value.
    WriteHexB Displays the contents of the RAX register as a hexadecimal integer in either a 1-byte, 2-byte, 4-byte, or 8-byte format. Pass it the display size (1, 2, 4, or 8) in the RBX register. It has no return value.
    WriteString Displays a null-terminated ASCII string. Pass it the string’s 64-bit offset in RDX. It has no return value.
    2). 调用64位子程序
    ExitProcess PROTO   ; Windows API 中
    WriteInt64 PROTO    ; Irvine64 库中
    
        call ExitProcess    ; 调用
    
    3). 调用过程示例
    ; Calling a subroutine in 64-bit mode           (CallProc_64.asm)
    ; 添加现有项Irvine64.obj
    
    ExitProcess PROTO   ; Windows API 中
    WriteInt64 PROTO    ; Irvine64 库
    Crlf PROTO          ; Irvine64 库
    
    .code
    main PROC
        sub rsp, 8          ; 对齐栈指针
        sub rsp, 20h        ; 保留32个字节
    
        mov rcx, 1          ; 设置参数
        mov rdx, 2
        mov r8, 3
        mov r9, 4
    
        call AddFour        ; 返回值赋给RAX
        call WriteInt64     ; 显示数字
        call Crlf           ; 输出空行
    
        mov ecx, 0  
        call ExitProcess
    main ENDP
    
    AddFour PROC
        mov rax, rcx
        add rax, rdx
        add rax, r8
        add rax, r9         ; 四个数之和放在RAX中
        RET
    AddFour ENDP
    
    END
    

    提示:该程序需要链接Irvine64.obj文件,在解决方案资源管理器窗口右键工程项目名 -> 添加 -> 现有项... -> 选择Irvine64.obj文件,如果出现Irvine64.obj : error LNK2017: 没有 /LARGEADDRESSAWARE:NO,“ADDR32”到“bufferLHB”的重定位无效问题,项目名右键 -> 属性 -> 配置属性 -> 链接器 -> 系统 -> 启用大地址 -> 选择否(/LARGEADDRESSAWARE:NO), 原文地址

    /LARGEADDRESSAWARE:NO.png

    相关文章

      网友评论

        本文标题:汇编开发(三):程序

        本文链接:https://www.haomeiwen.com/subject/dsxeeqtx.html