作为非计算机专业的孩子,想要了解每一条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

堆栈数据指针

通用寄存器:

汇编 —— 起步_数据

半寄存器:

汇编 —— 起步_数据_02

指针寄存器: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查看内存地址的存储值:

$ gdb exe
(gdb) start
Temporary breakpoint 1 at 0x804845c: file x.c, line 4.
Starting program: /home/edemon/workspace/exe

Temporary breakpoint 1, main () at x.c:4
4 char *p = (char *)malloc(1);
(gdb) n
5 *p = 'a';
(gdb) p p
$1 = 0x804b008 ""
(gdb) help x
Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char), s(string)
and z(hex, zero padded on the left).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The specified number of objects of the specified size are printed
according to the format.

Defaults for format and size letters are those previously used.
Default count is 1. Default address is following last thing printed
with this command or "print".
(gdb) x/1ub 0x804b008
0x804b008: 0
(gdb) x/1ub 0xb7fbd0a0
0xb7fbd0a0 <environ>: 12
(gdb) x/3ub 0xb7fbd0a0
0xb7fbd0a0 <environ>: 12 245 255

#在调试中查看各个寄存器的值
(gdb) info all-register
eax 0xb7fbd0a0 -1208233824
ecx 0xbffff470 -1073744784
edx 0xbffff494 -1073744748
ebx 0xb7fbb000 -1208242176
esp 0xbffff440 0xbffff440
ebp 0xbffff458 0xbffff458
esi 0x0 0
edi 0x8048350 134513488
eip 0x804845c 0x804845c <main+17>
eflags 0x282 [ SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
st0 0 (raw 0x00000000000000000000)
st1 0 (raw 0x00000000000000000000)
st2 0 (raw 0x00000000000000000000)
st3 0 (raw 0x00000000000000000000)
st4 0 (raw 0x00000000000000000000)
st5 0 (raw 0x00000000000000000000)
st6 0 (raw 0x00000000000000000000)
st7 0 (raw 0x00000000000000000000)
fctrl 0x37f 895
fstat 0x0 0
ftag 0xffff 65535
fiseg 0x0 0
---Type <return> to continue, or q <return> to

段寄存器是操作系统的工具。

汇编

汇编编程模型,实模式平面模型和保护模式平面模型的主要区别:在实模式平面模型下,自己写的程序拥有操作系统交给他的整个内存空间,在保护模式平面模型下,程序得到内存的一部分,其他仍然属于操作系统。
在实模式下计算机是开放的,保护模式下的计算机则限制了我们不能做一些事情,比如直接访问端口硬件,内存映射视频系统,直接调用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; ^_^

分析器的三类文件:

FILES
"a.out"
the namelist and text space.

"gmon.out"
dynamic call graph and profile.

"gmon.sum"
summarized dynamic call graph and

tag:
分析器的使用如下:

edemon@ubuntu1:~/workspace$ gcc hello.c -pg -o hello -O2 -lc
edemon@ubuntu1:~/workspace$ ./hello
hello
edemon@ubuntu1:~/workspace$ ls |grep gmon
gmon.out
edemon@ubuntu1:~/workspace$ gprof hello gmon.out -p

Flat profile:

Each sample counts as 0.01 seconds.
no time accumulated

% cumulative self self total
time seconds seconds calls Ts/call Ts/call name

% the percentage of the total running time of the
time program used by this function.

cumulative a running sum of the number of seconds accounted
seconds for by this function and those listed above it.

self the number of seconds accounted for by this
seconds function alone. This is the major sort for this

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”不再是一件容易的事情。我的第一个汇编程序是简单的寄存器存值(够简单吧)。
内容:

.section .text
.global main
main:
movl $100, %eax
int $0x80

​int $0x80​​​是一个Linux系统调用,从内核访问控制台显示。
用gdb执行并查看寄存器的值

[edemon@CentOS asm]$ gcc -gdwarf-2 -o mov mov.s
[edemon@CentOS asm]$ gdb mov
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)
...
Starting program: /home/edemon/workspace/asm/mov

Temporary breakpoint 1, main () at mov.s:4
4 movl $100, %eax
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.192.el6.i686
(gdb) n
5 int $0x80
(gdb) info register
eax 0x64 100
ecx 0xeb17dccc -350757684
edx 0x1 1
ebx 0x3a3ff4 3817460
esp 0xbffff22c 0xbffff22c
ebp 0xbffff2a8 0xbffff2a8
esi 0x0 0
edi 0x0 0
eip 0x80483f1 0x80483f1 <main+5>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) n
0x080483f3 in

可以发现eax已经变成100.


hello world汇编:
两天之后,我发现linux下的AT&T汇编hello world原来也挺简单的 (多亏能调用C的函数)。哈哈哈,总算入门了。(16.12.20)

.section .data
output:
.asciz "hello world\n"
.section .text
.globl main
main:
pushl $output
call printf
call

编译执行:

[edemon@CentOS workspace]$ gcc cmov.s
[edemon@CentOS workspace]$ ./a.out
hello world