作为非计算机专业的孩子,想要了解每一条C语句到底发生了什么,学习汇编也就变得水到渠成了。经过好几天的折腾,总算搞懂了一点点,一开始看王爽老师的《汇编语言 第三版》,讲得确实不错,但是8086cpu的汇编环境确实有点老,装了一个DOSBos,debug.exe倒是能用了,但是edit,masm啥的全都没有啊,更重要的是将来的工作都是在linux上进行,故而学到第四章就放弃了,转而学习linux环境的汇编。
语言
在操作的最底层,计算机处理器按照制造厂商在处理器芯片内部定义的二进制代码操作数据,预置的代码称之为指令码,不同的处理器包含不同类型的指令码。
高级语言分为编译语言(编译生成指令码,指令码生成可执行文件),解释语言(由单独的程序读取和执行,程序代码转换成指令码),混合语言(包含前两种语言的特点,比如java语言,编译生成字节码再由java虚拟机JVM解释)。
计算机结构
存储器由一些可变模式的容器组成。
晶体管开关及其支持部件合在一起称之为存储单元。
计算机中的成员:晶体管、二极管、硅、半导体、电容。
Intel的32位体系结构叫做IA-32,AMD创造的向后兼容的64位x86体系结构就做x86_64.
cpu为了完成指令让它做的事情而依赖的所有的电机制统称为CPU的微体系结构。
操作系统可以为任务列表中的每一个程序赋予一个优先级,高优先级的任务获得更多的时钟周期,低优先级的任务获得较少的时钟周期。
linux内核和图形用户界面是完全分开的,系统寄存器或被标识为内核空间,或被标识为用户空间,运行在用户空间的任何内容都不能被写入内核空间中,也不能从内核空间读取内容。只有运行在内核空间中的软件才能直接访问物理硬件。
CPU的效率取决于它一次能调集的地址线根数。
寄存器
IA-32处理器
寄存器 | 作用 |
通用 | 8个32位用于存储数据的存储器 |
段 | 6个16位处理内存访问的寄存器 |
指针 | 32位寄存器,指向下一条指令码 |
浮点数据 | 8个80位寄存器,用于浮点数学计算 |
控制 | 5个32位用于确定处理器操作模式的处理器 |
调试 | 8个32位包含调试信息的处理器 |
32位x86体系结构的8个通用寄存器:EAX, EBX, ECX, EDX, EBP, ESI, EDI, ESP.
通用寄存器 | 作用 |
EAX | 操作数和结果数据的累加器 |
EBX | 指向数据段内存中数据的指针 |
ECX | 字符串和循环操作的计数器 |
EDX | I/O指针 |
EDI | 字符串操作的目标的数据指针 |
ESI | 字符串操作的源的数据指针 |
ESP | 堆栈指针 |
EBP | 堆栈数据指针 |
通用寄存器:
半寄存器:
指针寄存器:EIP寄存器,跟踪下一条指令码。
段寄存器 | 作用 |
CS | 代码段 |
DS | 数据段 |
SS | 堆栈段 |
ES | 附加段指针 |
FS | 附加段指针 |
GS | 附加段指针 |
控制寄存器 | 描述 |
CR0 | 控制操作模式和处理器状态的系统标志 |
CR1 | |
CR2 | 内存页面错误信息 |
CR3 | 内存页面目录信息 |
CR4 | 说明处理器特性或能力的标志 |
内存
RAM: 随机访问存储,不会影响其他内容,就像在图书馆检索后找书一样。
ROM: 顺序访问,依次搜索。
一旦一个内存地址被应用到地址管脚上,数据管脚就会发生变化。
一个字节(byte)由8位(bit)
名称 | 内存单元 |
字节 | 1 |
字 | 2 |
双字 | 4 |
四字 | 8 |
十字节 | 10 |
段落 | 16 |
页 | 256 |
段 | 65536 |
字单元:存放一个字型数据的内存单元,16位,由两个连续的内存单元组成。
任何两个连续的内存单元都可以看作一个字单元。
一次处理双字的计算机即32位,一次处理4字的计算机就是64位,多少位就是针对处理器来说的。
中央处理单元(CPU)从内存中独入一个字节(双字、四字等)时,它将该字节的内存地址放到他的地址管脚上,编码成二进制数,字节随后出现在数据管脚上。
每一个CPU都含有存储数据的地方,称之为寄存器。
cpu在指针寄存器的指引下行进,其工作的本质就是将不同的机器指令字节压入8个晶体管寄存器中。
数据总线上一根线对应1位(bit),地址总线上对应一个存储单元 (字节,byte)
cpu在段地址中进行读写操作就是在物理存储器中进行读写操作。
地址总线影响着内存的大小。
内存 | 段地址 | 偏移地址 |
数据段 | DS | SI |
代码段 | CS | IP |
栈段 | SS | SP |
一段内存可以同时是代码段、数据段、栈空间,也可以啥也不是。
使用gdb查看内存地址的存储值:
段寄存器是操作系统的工具。
汇编
汇编编程模型,实模式平面模型和保护模式平面模型的主要区别:在实模式平面模型下,自己写的程序拥有操作系统交给他的整个内存空间,在保护模式平面模型下,程序得到内存的一部分,其他仍然属于操作系统。
在实模式下计算机是开放的,保护模式下的计算机则限制了我们不能做一些事情,比如直接访问端口硬件,内存映射视频系统,直接调用BIOS。
汇编语言程序的三个组件:操作码助记符(push, mov, sub等)、数据段、命令(比如.section命令)。
考虑到移植性,很多编译器编码遵守“最小公分母”原则,所以程序的执行速度并不是最快的。
汇编语言程序一般就有三个段落:数据段(.data,声明带有初始值的数据元素),静态内存段(.bss,声明使用零值初始化的数据元素),文本段(.text)。
处理器通过地址总线、数据总线、控制总线和计算机的系统内存、输入设备、输出设备相连。
intel处理器官网:www.intel.com
反汇编(Disassembly):把目标代码转为汇编代码的过程
下面是汇编学习环境:
tool | info | use example |
汇编器 | AS - the portable GNU assembler | as -o test.o test.s |
连接器 | ld - The GNU linker | ld -o test test.o |
调试器 | gdb - The GNU Debugger | gdb a.out |
编译器 | gcc - GNU project C and C++ compiler | gcc hello.c |
反汇编器 | objdump - display information from object files | objdump -d a.out > a.s |
分析器 | gprof - display call graph profile data | goto tag; ^_^ |
分析器的三类文件:
tag:
分析器的使用如下:
GNU连接器ld查找的是_start来确定程序开始的位置,但是gcc查找的是main标签,所以我们需要修改:
所以如果是使用gcc汇编和连接汇编程序,那么将_start改成main即可。
调试汇编程序,as的调试选项[--gstabs+] [--gdwarf-2] [--gdwarf-sections]
。
例如:
as -gstabs -o test.o test.s
gdb中使用print打印2进制、10进制、16进制的格式:print/t, print/d, print/x
系统:Linux ubuntu1 3.19.0-74-generic #82-Ubuntu SMP Thu Oct 20 21:46:04 UTC 2016 i686 i686 i686 GNU/Linux
标准的C动态库文件:/lib/i386-linux-gnu/libc.so.6
Intel和AT&T的区别:
AT&T使用$表示立即操作数,Intel不需要。
AT&T在寄存器名称前使用&,Intel不需要。
AT&T的source、destination的顺序和Intel是相反的,比如AT&T的mov: movx source, destination
, Intel的mov:mov desctination, source
AT&T在助记符后面使用单独的字符表示数据长度,而Intel使用单独的操作数表示长度。
定义段和偏移值,AT&T: ljmp $section,$offset
, Intel使用 jmp section:offset
。
第一个程序
和高级语言不同的是,在汇编中写一个“hello world”不再是一件容易的事情。我的第一个汇编程序是简单的寄存器存值(够简单吧)。
内容:
int $0x80
是一个Linux系统调用,从内核访问控制台显示。
用gdb执行并查看寄存器的值
可以发现eax已经变成100.
hello world汇编:
两天之后,我发现linux下的AT&T汇编hello world原来也挺简单的 (多亏能调用C的函数)。哈哈哈,总算入门了。(16.12.20)
编译执行: