什么是汇编

汇编(Assembly Language)是针对特定处理器架构得一种低级语言。它使用的是人易于识记的符号来代表机器指令,即机器码(Opcode),以及内存的相对地址。机器码和每一条汇编的指令是可以一一对应的,但在其他高级语言中却不一定可以。在执行一个二进制文件时,计算机真正做的是按周期执行的是一个个机器指令。

汇编可以直接对各种硬件部件操作,但是实现的代价十分高昂。汇编可以说是比机器码编程仅仅高一级。每一条指令执行一个简单的操作(当然现在是有指令可以一条搞定两个浮点数的乘法的,但大部分广泛使用的指令操作却并不复杂),结果是你哪怕向实现一个简单的功能都可能动辄上千条指令。而且相比于其他语言而言汇编基本上就是结构化编程的反面例子,各种流程用的是跳转实现。

为什么是汇编

或者说为什么学汇编?汇编有什么用?我的回答是:汇编的确没有什么用,但是学习的目的是源于提问者自身

在今天,任何一种高级语言都可以比汇编更容易上手,更容易编写,更容易移植,汇编可以说是一门已经过气的语言,实用价值并不高。但是在实际情况下,你还是会不经意地碰到它:

  • 参加CTF,拿到逆向的题之后发现要人肉反编译算法,你只能阅读汇编分析(当然一上来就开IDA按F5的或者直接一把梭的当我没有说)
  • 某天在调试一个编译器优化后的程序出现了玄学问题,例如打不上断点,或者执行结果有误,这时你要在在汇编级别的代码调试排除问题(这时确实可以关掉优化,但这不是一劳永逸的)
  • 在Github上阅读代码时,发现编写者为了优化运行速度或者实现硬件功能而采用了内联汇编或者完全汇编的代码。实际上,Linux Kernel的源码中有一部分是内联汇编
  • ……

为了达成其他目的而学习汇编,像极了学习数学的过程。虽然说我们日常应用中汇编编程的机会微乎其微,但是学习的过程会让你对某些计算机相关的内容有更深的理解。比如说你可以回答上来堆栈是什么?它是怎么运作的?x86中的保护模式和实模式是什么?在底层的角度上看函数调用的过程是怎样的?在解决“计算机中的XX究竟是如何运行”这类问题中,汇编或许能给你很多详细的非纯硬件层面的解答。

怎么学汇编

这个问题严格上说挺空泛的。这就像有个人跑过来问你:我应该怎么学编程?你可能不知道怎么回答或者给他贴个C语言的教程就算了。汇编也有很多种,跟CPU架构直接相关,前面已经说过了。可以是Intel的IA-32指令集(或者说x86指令集),ARM的Thumb-2,还有可能在你家路由器上跑的MIPS指令集等。其中x86作为目前PC主流的架构我们一般非常容易接触,所以我们一般讨论学汇编的时候都是针对x86。如果说是要对各种CPU底层有个基本印象的话,其余两种可以跳过,除非你以后打算做嵌入式天天和ARM的处理器打交道的工作。

首先要知道指令集和执行环境里面有什么,这个课本上应有,纯工具性内容可以直接查得到

其次是运作原理,这些指令做了什么?它们是如何相互作用的?

还有这些指令中对事件的处理机制(中断、异常)是怎样的?

如果说想系统性学习汇编的,可以看看这本《汇编语言:基于x86处理器》,不过是以Windows下的MASM为基础的,对Linux用户不太友好

如果说其他网上的资料太零散的话可以试试看这本书 Intel® 64 and IA-32 Architectures Software Developer’s Manual,Intel官方的编程手册,不过是全英文版的。本来是一本用来随时查阅的工具书,但前面几章其中有对32位和64位CPU指令和执行环境详尽的描述,前面讲环境的内容很全,耐心看完它应该上手阅读汇编不难

如果你想看看一些Linux汇编的代码的话,可以试着用把你自己曾经写过的C代码用gcc汇编:gcc -S test.c ,看看编译器给出的汇编是什么样的。你还可以调用一些函数或者加入一些控制语句,观察编译器是怎么处理。这也不妨为一种原始的学习方式。另外,如果你还懂一些gdb的调试方法的话,你还可以把你自己写的代码切换到反汇编,然后单步执行观察堆栈和寄存器的变化情况,加深一下印象。如果是在Windows下的话可以用MinGW来引入gcc和gdb

最后,祝你在学汇编的路上找到那种对原来知识深入理解后“恍然大悟”的乐趣😆

    我这一周在看汇编,当然不是因为18级要上课。而是因为在看一些论文的时候发现,很多前沿的理论研究者把自己的理论实现的时候往往已经在考虑系统优化问题,写了汇编代码。

    如果给自己一个不学的理由也是可以的,毕竟学海无涯,每个人都有自己的短板。坏处就是,长期下去,短板会越来越多。

    Helloe, world! Assembly langguage... 下面这段代码Mac OS下Nasm可以运行。

    ; nasm -f macho64 hello.asm 
    ;ld -macosx_version_min 10.7.0 -lSystem -o hello hello.o 
    ; ./hello
    
    global start
    
    section .text
    
    start:
        mov     rax, 0x2000004 ; write
        mov     rdi, 1 ; stdout
        lea     rsi, [msg]
        mov     rdx, msg.len
        syscall
    
        mov     rax, 0x2000001 ; exit
        mov     rdi, 0
        syscall
    
    
    section .data
    
    msg:    
       default rel
       db      "Hello, world!", 10
    .len:   equ     $ - msg

    补充:如何搭建一个编写汇编的环境

    上次忘记提到了这一点,毕竟虽然汇编不是经常要写,但是学的时候还是亲手写写好。Windows下有专属的MASM,还有Linux和Windows通用的NASM。Windows上的都是开箱即用的,然而Linux的方案通常都不是现成的。在Linux环境下另外一个办法就是用GAS(GNU Assembler)

    在一个CS汇编课程里可以找到这样的Hello World例子:

            .global _start
    
            .text
    _start:
            # write(1, message, 13)
            mov     $1, %rax                # system call 1 is write
            mov     $1, %rdi                # file handle 1 is stdout
            mov     $message, %rsi          # address of string to output
            mov     $13, %rdx               # number of bytes
            syscall                         # invoke operating system to do the write
    
            # exit(0)
            mov     $60, %rax               # system call 60 is exit
            xor     %rdi, %rdi              # we want return code 0
            syscall                         # invoke operating system to exit
    message:
            .ascii  "Hello, world\n"

    上述代码保存为Hello.s,然后用以下命令汇编并链接

    gcc -c hello.s && ld hello.o

    就可以得到Linux下一个可执行的ELF文件,在此基础上还可以加个SASM作为IDE

    虽然我还没有找到基于这种环境教材,不过这里的课程笔记可以参考一下。当然还有另外一种办法就是上面提到的内联汇编,目前我们常用的C编译器都支持这个特性。也就是说,理论上符合格式的代码可以用内联汇编的办法写在main函数里面,然后用gcc或者Windows下VS的编译器编译,gcc版的大概长这样:

    int main(){
    	__asm__  __volatile__(
    		//此处一行一指令,指令用双引号引起来,比如
    		"mov $eax , 1\n\t"
    	);
    	return 0;
    }

    我从初学角度来说一下自己的体会,我是学完C语言然后去学习汇编的,本专业是机械,完全是靠兴趣学习的,说的不好的地方请大家指正。
    我是跟着王爽老师的那本《汇编语言》学的,跟着小甲鱼的课学了一个寒假。我认为学习汇编是为了体验底层编程,去体验机器是怎么思考的,因为汇编其实就是机器语言了(只不过是隔了符号转换)。
    汇编语言能让我们理解CPU与内存之间的关系,程序的运作空间也就是在这两个硬件上面。那么我认为学汇编的目的就显现出来了,为了理解CPU与内存之间的关系,一方面从而更好理解程序在底层的运行,另一方面以此为基础来理解其他底层硬件的作用。但是要搞逆向的话,我就不知道该怎么说了。。。。估计楼主就是搞逆向的,不然应该不用学那么深吧。。。

    还是觉得汇编枯燥无味怎么办?

    你可以试试这个编程解谜类的游戏。涉及到许多与算法和优化的部分,如果你对算法感兴趣的话正好借此以汇编的形式写算法。游戏的设定是以汇编形式对一个小人编程,完成指定任务

    当然跟ACM类似的,有两个挑战的标准:指令数量和平均运行时间,如果玩家觉得完成任务太简单(实际上我觉得有编程基础的人打通主线应该没有问题),可以往这两个方向想办法如何优化自己的程序
    如果你实在想不出来的话,Github上有不少仓库放了优化的题解,可以看看别人是怎么写的

    虽然算法和本贴的主题没有直接关联,但是我相信有些优化的技巧应该是和编译器生成目标代码的优化相关的。能够激起你的兴趣去不断学习理论,我觉得这就够了😃
    (我居然在0xFFFF上推荐游戏

    1 个月 后
    2 个月 后

    上周学了一些RISC-V,这两天把x86过了一遍,观察到:

    1 目前的汇编语言相比40/50年前已经高级很多了,提供了很多方便的功能比如一些字符串操作(至少单字符可以直接表达,不需要通过数字)、栈操作。x86里连.repeat .until都有了。
    这点总而言之就是与高级语言的基本逻辑无缝对接了,只是非常罗嗦。

    2 指令集的复杂性与基本指令组合以及针对常用(组合)指令的优化关系很大。

    3 CPU和内存之间的关系是不同指令集的主要差别,比如有的寄存器特别少,有的比较多更好支持在ALU内计算。

    对个人的启发:

    1 从编程语言角度,应该把重点从高级语言在汇编层面的直接实现(implementation)转向高级语言到中间表示(intermediate representation)这一段。十年前虚拟机比如JVM还是比较小众而神秘的东西,现在呢,虚拟化已经成为主流,各种新语言的运行环境多在虚拟机中,老牌语言也在调整。这个列表很说明问题:https://en.wikipedia.org/wiki/Comparison_of_application_virtualization_software

    2 硬件层面的东西相应来说更复杂了,以比较新的x86为例,汇编语言转成机器语言后还有复杂的处理器层面的优化,这个在80年代就有了但由于闭源性人们不太了解或者了解了也没用就不太关注了。

    3 年 后

    © 2018-2025 0xFFFF