Assembly Language汇编语言入门


备注

程序集是用于许多人类可读形式的机器代码的通用名称。它在不同的CPU(中央处理单元)之间自然会有很大差异,但在单CPU上可能存在几种不兼容的Assembly方言,每种方言由不同的汇编程序编译成CPU创建者定义的相同机器代码。

如果您想询问有关自己的汇编问题的问题,请始终说明您正在使用的HW和汇编程序,否则将很难详细回答您的问题。

学习单个特定CPU的组装将有助于学习不同CPU的基础知识,但每个硬件架构在细节上都有很大差异,因此学习新平台的ASM可以接近从头开始学习。

链接:

X86 Assembly Wikibook

介绍

汇编语言是机器语言或机器代码的人类可读形式,它是处理器逻辑运行的实际位和字节序列。人类通常比二进制,八进制或十六进制更容易阅读和编程助记符,因此人们通常用汇编语言编写代码,然后使用一个或多个程序将其转换为处理器理解的机器语言格式。

例:

mov eax, 4
cmp eax, 5
je point
 

汇编程序是一个程序,它读取汇编语言程序,解析它并生成相应的机器语言。重要的是要理解,与C ++之类的语言(标准文档中定义的单一语言)不同,有许多不同的汇编语言。每个处理器体系结构,ARM,MIPS,x86等具有不同的机器代码,因此具有不同的汇编语言。此外,对于相同的处理器体系结构,有时会有多种不同的汇编语言。特别是,x86处理器系列有两种流行的格式,通常称为气体语法gas 是GNU Assembler可执行文件的名称)和Intel语法 (以x86处理器系列的发起者命名)。它们是不同的但是相同的,因为通常可以用任一语法编写任何给定的程序。

通常,处理器的发明者记录处理器及其机器代码并创建汇编语言。这种特定汇编语言通常是唯一使用的汇编语言,但与编译器编写者试图符合语言标准不同,处理器发明者定义的汇编语言通常但并不总是编写汇编程序的人使用的版本。 。

有两种通用类型的处理器:

  • CISC(复杂指令集计算机):有许多不同且通常很复杂的机器语言指令

  • RISC(精简指令集计算机):相比之下,指令更少,更简单

对于汇编语言程序员来说,不同之处在于CISC处理器可能需要学习很多指令,但通常有适合特定任务的指令,而RISC处理器的指令更少且更简单,但任何给定的操作都可能需要汇编语言程序员写更多指令以完成同样的事情。

其他编程语言编译器有时会首先生成汇编程序,然后通过调用汇编程序将其编译为机器代码。例如, gcc在编译的最后阶段使用自己的气体汇编程序。生成的机器代码通常存储在目标文件中, 目标文件可以通过链接器程序链接到可执行文件中。

完整的“工具链”通常由编译器,汇编器和链接器组成。然后,可以直接使用汇编程序和链接程序以汇编语言编写程序。在GNU世界中, binutils包包含汇编器和链接器以及相关工具;那些只对汇编语言编程感兴趣的人不需​​要gcc或其他编译器包。

小型微控制器通常仅以汇编语言或汇编语言与一种或多种更高级语言(如C或C ++)的组合编程。这样做是因为人们经常可以使用指令集架构的特定方面来为这些设备编写比在更高级语言中更高的紧凑,高效的代码,并且这种设备通常具有有限的存储器和寄存器。许多微处理器用于嵌入式系统 ,这些嵌入式系统是通用计算机以外的设备,其恰好在内部具有微处理器。这种嵌入式系统的例子是电视机,微波炉和现代汽车的发动机控制单元。许多这样的设备没有键盘或屏幕,因此程序员通常将程序写在通用计算机上,运行交叉汇编程序 (因为这种汇编程序为不同类型的处理器生成代码而不是运行它的处理器)和/或交叉编译器交叉链接器以生成机器代码。

这些工具有许多供应商,它们与生成代码的处理器一样多种多样。许多(但并非所有)处理器都有一个开源解决方案,如GNU,sdcc,llvm或其他。

在Visual Studio 2015中执行x86程序集

第1步 :通过File - > New Project创建一个空项目。

在此处输入图像描述

第2步 :右键单击项目解决方案,然后选择Build Dependencies-> Build Customizations

第3步 :选中“.masm”复选框。

在此处输入图像描述

第4步 :按“确定”按钮。

第5步 :创建程序集文件并输入:

.386
    .model small
        .code

            public main
                main proc

                    ; Ends program normally

                    ret
                main endp
            end main
 

第6步 :编译!

在此处输入图像描述

适用于Linux x86_64的Hello world(Intel 64位)

section .data
    msg db "Hello world!",10      ; 10 is the ASCII code for a new line (LF)

section .text
    global _start

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, 13
    syscall
    
    mov rax, 60
    mov rdi, 0
    syscall
 

如果要执行此程序,首先需要Netwide Assemblernasm ,因为此代码使用其语法。然后使用以下命令(假设代码位于文件helloworld.asm )。它们分别用于组装,链接和执行。

  • nasm -felf64 helloworld.asm
  • ld helloworld.o -o helloworld
  • ./helloworld

该代码使用了Linux的sys_write 系统调用。 在这里,您可以看到x86_64架构的所有系统调用列表。当您还将写入退出的手册页考虑在内时,您可以将上述程序转换为C语言,它可以执行相同操作并且更具可读性:

#include <unistd.h>

#define STDOUT 1

int main()
{
    write(STDOUT, "Hello world!\n", 13);
    _exit(0);
}
 

这里只需要两个命令来进行编译和链接(第一个)和执行:

  • gcc helloworld_c.c -o helloworld_c
  • ./helloworld_c

OS X的Hello World(x86_64,Intel语法气体)

.intel_syntax noprefix

.data

.align 16
hello_msg:
    .asciz "Hello, World!"

.text

.global _main
_main:
    push rbp
    mov rbp, rsp

    lea rdi, [rip+hello_msg]
    call _puts

    xor rax, rax
    leave
    ret
 

组装:

clang main.s -o hello
./hello
 

笔记:

  • 不鼓励使用系统调用,因为OS X中的系统调用API不被认为是稳定的。而是使用C库。 ( 参考Stack Overflow问题
  • 英特尔建议大于一个字的结构从16字节边界开始。 ( 参考英特尔文档
  • 订单数据通过寄存器传递到函数中:rdi,rsi,rdx,rcx,r8和r9。 ( 参考System V ABI

机器代码

机器代码是特定本机数据格式的术语,由机器直接处理 - 通常由称为CPU (中央处理单元)的处理器处理。

通用计算机体系结构( 冯诺依曼体系结构 )由通用处理器(CPU),通用存储器组成 - 存储程序(ROM / RAM)和处理数据以及输入和输出设备(I / O设备)。

这种架构的主要优点是每个组件的相对简单性和通用性 - 与之前的计算机机器(机器结构中的硬连线程序)或竞争架构(例如哈佛架构将程序存储器与存储器的内存分开)相比数据)。缺点是总体性能稍差。从长远来看,普遍性允许灵活使用,这通常超过了性能成本。

这与机器代码有什么关系?

程序和数据作为数字存储在这些计算机中的内存中。没有真正的方法来区分数据代码,因此操作系统和机器操作员在将所有数字加载到内存后给出CPU提示,在该提示处,内存的入口点启动程序。然后CPU读取存储在入口点的指令(数字),并严格处理,顺序读取下一个数字作为进一步的指令,除非程序本身告诉CPU继续在别处执行。

例如,两个8位数字(8位组合在一起等于1个字节,这是0-255范围内的无符号整数): 60 201 ,当作为Zilog Z80上的代码执行时,CPU将作为两个指令处理: INC a (将寄存器a 的值递增1)和RET (从子程序返回,指向CPU执行来自存储器不同部分的指令)。

为了定义该程序,人类可以通过某些存储器/文件编辑器输入这些数字,例如在十六进制编辑器中输入两个字节: 3C C9 (以16进制编码写入的十进制数60和201)。那将是机器代码编程

为了使人类更容易编写CPU编程任务,创建了一个Assembler程序,能够读取包含以下内容的文本文件:

subroutineIncrementA:
    INC   a
    RET

dataValueDefinedInAssemblerSource:
    DB    60          ; define byte with value 60 right after the ret
 

输出字节十六进制数字序列3C C9 3C ,包含特定于目标平台的可选附加数字:标记此类二进制文件的哪一部分是可执行代码,其中是程序的入口点(其中的第一条指令),哪些部分是编码的数据(不可执行)等

注意程序员如何将值为60的最后一个字节指定为“数据”,但从CPU的角度来看,它与INC a 字节没有任何区别。由执行程序正确导航CPU作为指令准备的字节,并仅将数据字节作为指令数据处理。

这样的输出通常存储在存储设备上的文件中,稍后由OS( 操作系统 - 已经在计算机上运行的机器代码,有助于与计算机一起操作 )在执行之前加载到内存中,最后将CPU指向程序的切入点。

CPU只能处理和执行机器代码 - 但是任何内存内容,甚至是随机内存,都可以这样处理,虽然结果可能是随机的,从OS检测和处理的“ 崩溃 ”到意外擦除I /中的数据O设备,或连接到计算机的敏感设备的损坏(不是家用电脑的常见情况:))。

许多其他高级编程语言遵循类似的过程,将 (人类可读的文本形式的程序)编译成数字,或者代表机器代码(CPU的本机指令),或者在解释/混合语言的情况下编译成一般的特定于语言的虚拟机代码,在解释器或虚拟机执行期间进一步解码为本机机器代码。

有些编译器使用Assembler作为编译的中间阶段,首先将源转换为Assembler形式,然后运行汇编工具从中获取最终的机器代码 (GCC示例:运行gcc -S helloworld.c 以获取C程序的汇编程序版本) helloworld.c )。