08GNU as汇编

1. 概述

​ 由于操作系统许多关键代码要求有很高的执行速度和效率,因此在一个操作系统源代码中通常就会包含大约 10% 左右的起关键作用的汇编语言程序量。Linux 操作系统也不例外,它的 32 位初始化代码、所有中断和异常处理过程接口程序、以及很多宏定义都使用了 as 汇编语言程序或扩展的嵌入式汇编语句。

​ 在编译 C 语言程序时, GNU gcc 编译器会首先输出一个作为中间结果的 as 汇编语言文件,然后 gcc 会调用 as 汇编器把这个临时汇编语言程序编译成目标文件。即实际上 as 汇编器最初是专门用于汇编 gcc 产生的中间汇编语言程序的,而非作为一个独立的汇编器使用。因此,as 汇编器也支持很多 C 语言特性,这包括字符、数字和常数标识方法以及表达式形式等方面。

2. 汇编程序预处理

​ as 汇编器具有对汇编语言程序内置的简单预处理能力。

该预处理功能会调整并删除多余的空格字符和制表符;
删除所有注释语句并使用单个空格或一些换行符替换它们;
把字符常数转换成对应的数值。

​ 但是该预处理功能不会对宏定义进行处理,也没有处理包含文件的功能。如果需要这方面的功能,那么可以让汇编语言程序使用大写的后缀“.S” 让 as 使用 gcc 的 CPP 预处理功能。

​ 由于 as 汇编语言 程序除了使用 C 语言块注释语句(即 /* */)以外,还使用井号 “#” 作为单行注释开始字符,因此若在汇编之前不对程序执行预处理,那么程序中包含的所有已井号 “#” 开始的指示符或命令均会被当做注释部分。

3. 符号、语句和常数

​ 符号(Symbol)是由字符组成的标识符,组成符号的有效字符取自于大小写字符集、数字和三个字符 “_ . $”。符号不允许用数字字符开始,而且区分大小写。在 as 汇编程序汇总符号长度没有限制,并且符号中所有字符都是有效的。符号使用其他字符(例如空格、换行符)或者文件的开始来界定开始和结束处。

​ 语句(Sttatement)以换行符或者行分割字符(‘;’)作为结束,文件最后语句必须以换行符作为结束。若在一行的最后使用反斜杠 ‘ \ ’ (在换行符前),那么就可以让一条语句使用多行。当 as 读取到反斜杠加换行符时,就会忽略掉这两个字符。

​ 常数是一个数字,可 分为字符常数和数字常数两类。字符常数还可以分为字符串和单个字符;而数字常数可分为整数、大数和浮点数。

​ 字符串必须使用双引号括住,并且其中可以使用反斜杠 ' \' 来转义包含的特殊字符。例如 ‘ \\ ’ 表示一个反斜杠字符。

​ 整数数字常数有 4 中表示方法,即使用 ‘0b’ 或 ‘0B’ 开始的二进制数(‘0-1’);以 ‘0’ 开始的八进制数(‘0-7’);以非 ‘0’ 数字开始的十进制数(‘0-9’)和使用 ‘0x’ 或 ‘0X’ 开头的十六进制数(‘0-9a-fA-F’)。若要表示负数,只需在前面添加负号‘ - ’。

​ 大数(Bignum)是位数超过 32 位二进制位的数,其表示方法与整数的相同。汇编程序中对浮点数常数的表示方法与 C 语言中的基本一样。由于内核代码中几乎不用浮点数,因此这里不再对其进行说明。

4. 指令语句、操作数和寻址

​ 指令(Instructions)是 CPU 执行的操作,通常指令也称作操作码(Opcode);操作数(Operand)是指令操作的对象;而地址(Address)是指定数据在内存中的位置。指令语句是程序运行时刻执行的一条语句,它通常可包含 4 个组成部分:

标号(可选);
操作码(指令助记符);
操作数(由具体指令指定);
注释

​ 一条指令语句可以包含有 0 个或最多 3 个用逗号分开的操作数。对于具有两个操作数的指令语句,第一个是源操作数,第二个是目的操作数。

5. 区与重定位

​ 区(Section)(也称为段、节或者部分)用于表示一个地址范围,操作系统将以相同的方式对待和处理在该地址范围中的数据信息。例如,可以有一个“只读”的区,我们只能从该区中读取数据而不能写入。区的概念主要用来表示编译器生成的目标文件(或可执行程序)中不同的信息区域,例如目标文件中的正文区或数据区。若要正确理解和编制一个 as 汇编语言程序,我们就需要了解 as 产生的输出目标文件的格式安排。

​ 链接器 ld 会把输入的目标文件中的内容按照一定规律组合生成一个可执行程序。当 as 汇编器输出一个目标文件时,该目标文件中的代码被默认设置成从地址 0 开始。此后 ld 将会在链接过程中未不同目标文件中的各个部分分配不同的最终地址位置。ld 会把程序中的字节块移动到程序运行时的地址处。这些块是作为固定单元进行移动的。他们的长度以及字节次序都不会被改变。这样的固定单元就被称作是区(或段、部分)。而为区分配运行时刻的地址的操作就被称为重定向(Relocation)操作,其中包括调整目标文件中记录的地址,从而让它们对应到恰当的运行时刻地址上。

​ 其实,as 汇编器输出产生的目标文件中至少具有 3 个区,分别被称为正文(text)、数据(data)和 bss 区。每个区都可能是空的。如果没有使用汇编命令把输出放置在 ‘.text’ 或 ‘.data’ 区中,这些区会仍然存在,但内容是空的。在一个目标文件文件中,其 text 区从地址 0 开始,随后是 data 区,再后面是 bss 区。

​ 当一个区被重定位时,为了让链接器 ld 知道哪些数据会发生变化以及如何修改这些数据。as 汇编器也会往目标文件中写入所需要的重定位信息。为了执行重定位操作,在每次涉及目标文件中的一个地址时,ld 必须知道:

目标文件中对一个地址的引用是从什么地方算起的?
该引用的字节长度是多少?
该地址引用的是哪个区? (地址) - (区的开始地址)的值等于多少?
对地址的引用与程序计数器 PC(Progarm-Counter)相关吗?

​ 实际上,as 使用的所有地址都可表示为:(区)+(区中偏移)。另外,as 计算的大多数表达式都有这种与区相关的特性。在下面说明中,我们使用记号 “{secname N}”来表示区 secname 中偏移 N。

​ 除了 text、data 和 bss 区,我们还需要了解绝对地址区(absolute 区)。当链接器把各个目标文件组合在一起时,sbsolute 区中的地址将始终不变。例如,ld 会把地址{absolute 0}"重定位"到运行时刻地址 0 处。尽管链接器在连接后绝不会把两个目标文件中的 data 区安排成重叠地址处,但是目标文件中的 absolute 区必会重叠而覆盖。

​ 另外还有一种名为“未定义的”区(Undefined section)。在汇编时不能确定所在区的任何地址都被设置成{undefined U},其中 U 将会在以后填上。因为数值总是有定义的,所以出现未定义地址的唯一途径仅涉及未定义的符号。

​ 类似地,区名也用于描述已链接程序中区的组。链接器 ld 会把程序所有目标文件中的 text 区放在相邻的地址处。我们习惯上所说的程序的 text 区实际上是指其所有目标文件 text 区组合构成的整个地址区域。对程序中 data 和 bss 区的理解也同样如此。

6. 链接器涉及的区

链接器 ld 只涉及如下 4 类区:

text区、data区 —— 这两个区用于保存程序。as 和 ld 会分别独立而同等地对待它们。
对其中text区的描述也同样适合于data区。
然而当程序在运行时,则通常text区是不会改变的。text区通常会被进程共享,其中含有指令代码合常数等内容。
程序运行时data区的内容通常是会变化的,例如,C 变量一般就存放在data区中。
bss区 —— 在程序开始运行时这个区中含有 0 值字节。该区用于存放未初始化的变量或作为公共变量存储空间。
虽然程序每个目标文件 bss 区的长度信息很重要,但是由于该区中存放的是 0 值字节,因此无需再目标文件中保存 bss 区。设置 bss 区的目的就是为了从目标文件中明确地排除 0 值字节。
absolute区 —— 该区的地址 0 总是 “重定位” 到运行时刻地址 0 处。如果你不想让 ld 在重定位操作时改变你所引用的地址,那么就使用这个区。从这种观点来看,你们可以把绝对地址称作是 “不可重定位的”:在重定位操作期间他们不会改变。
undefined区 —— 对不在先前所述各个区中对象的地址引用都属于本区。

下图是 3 个理想化的课重定位的例子。这个例子使用传统的区名称:".text"和".data"。其中水平轴表示内存地址。

image

posted @ 2018-07-09 11:07  洛克十年  阅读(382)  评论(0编辑  收藏  举报