转贴的一篇小技巧汇总

这篇文档实用性很强,它不是 讲某个命令的参数具体含义,而是从实际工作的角度来考虑,完成什么工作需要什么指令。文件系统操作是最基本的操作, 没有文件系统,操作系统根本就运行不了。下面是我们经常要做的一些事情。在下面具体参数意义不解释,要了解这些可以查询该命令帮助文档

1. 创建目录

mkdir

NO1. 在当前路径创建一级目录

[root@rehat root]# mkdir test

NO2. 在当前路径创建多级目录

[root@rehat root]# mkdir -p mytest/test1/test1_1

NO3. 在创建目录的同时给新建的目录赋权限

[root@rehat root]# mkdir -m 777 testmod

这样任何人对此目录都有任何权限

2. 复制文件与文件夹

cp

NO1. 复制指定目录的文件到当前目录,并重命名

[root@rehat root]# cp ~/.bashrc bashrc_bak

NO2. 强制复制指定目录的文件到当前目录,而不管当前目录是否含有该文件

[root@rehat root]# cp -f ~/.bashrc bashrc

NO2. 复制指定目录到当前目录

[root@rehat root]# cp -r /root/test .

[root@rehat root]# cp -r /root/test/ .

两者效果一样,在复制目录时,会将源路径的最后一级目录全部复制过去,包括它本身。

NO3. 复制指定目录的文件到指定目录

[root@rehat root]# cp ~/.bashrc /bak/.bashrc

NO4. 在复制时将源文件的全部属性也复制过来。若不指定参数,则目标文件与源文件属性可能不一致。

[root@rehat root]# cp -a ~/.bashrc /bak/.bashrc

NO5. 若两个文件夹要保证同步,一个文件的改了,另一个文件也跟着改,但是要保证两个文件的文件都是最新的。

[root@rehat root]# cp -u /src/.bashrc /bak_src/bashrc

3. 建立链接文件,包括硬链接与软链接

ln

NO1. 建立类似于 Windows 的快捷方式

[root@rehat root]# ln -s test.txt test.txt_slnk

NO2. 当想备份一个文件,但空间又不够,则可以为该文件建立一个硬连接。这样,就算原文件删除了,只要该

链接文件没被删除,则在存储空间里还是没有被删除。

[root@rehat root]# ln -l test.txt test.txt_hlnk

4. 删除文件

rm

NO1. 删除当前目录的文件

[root@rehat root]# rm test.txt

NO2. 强制删除当前目录的文件,不弹出提示

[root@rehat root]# rm -f test.txt

NO3. 强制删除整个目录,包括目录与文件全部删除,需要管理员权限

[root@rehat root]# rm -r -f test

5. 删除文件夹

rmdir

NO1. 删除一个空目录

[root@rehat root]# rmdir emptydir

NO2. 删除多级空目录

[root@rehat root]# rmdir -p emptydir/d1/d11

6. 挂载文件系统与卸载文件系统

mount / umount

NO1. 挂载光驱

[root@rehat root]# mount -t iso9660 /dev/cdrom /mnt/cdrom

NO2. 挂载光驱,支持中文

[root@rehat root]# mount -t iso9660 -o codepage=936,iocharset=cp936 /dev/cdrom /mnt/cdrom

NO3. 挂载 Windows 分区,FAT文件系统

[root@rehat root]# mount -t vfat /dev/hda3 /mnt/cdrom

NO4. 挂载 Windows 分区,NTFS文件系统

[root@rehat root]# mount -t ntfs -o iocharset=cp936 /dev/hda7 /mnt/had7

No5. 挂载 ISO 文件

[root@rehat root]# mount -o loop /abc.iso /mnt/cdrom

NO6. 挂载 软驱

[root@rehat root]# mount /dev/fd0 /mnt/floppy

NO7. 挂载闪盘

[root@rehat root]# mount /dev/sda1 /mnt/cdrom

NO8. 挂载 Windows 操作系统共享的文件夹

[root@rehat root]# mount -t smbfs -o username=guest,password=guest //machine/path /mnt/cdrom

NO9. 显示挂载的文件系统

[root@rehat root]# mount

[root@rehat root]# cat /etc/fstab 显示系统启动自动加载的文件系统

[root@rehat root]# cat /etc/mtab 显示当前加载的文件系统

7. 检查磁盘空间

df

NO1. 显示所有存储系统空间使用情况,同时显示存储系统的文件系统类型s

[root@rehat root]# df -aT

NO2. 显示指定文件系统的空间使用情况

[root@rehat root]# df -t ext3

NO3. 人性化显示各存储空间大小

[root@rehat root]# df -ah

NO4. 有时候挂载了网络文件系统,若只想看本机的文件系统用如下命令

[root@rehat root]# df -ahlT

NO5. 查看某个文件系统的磁盘使用情况

[root@rehat root]# df -h /dev/cdrom

8. 检查目录空间大小

du

NO1. 查看当前文件夹大小

[root@rehat root]# du -sh

NO2. 查看当前文件及文件中包含的子文件夹大小

[root@rehat root]# du -ch

NO3. 查看文件的大小

[root@rehat root]# du -h test1.txt

NO4. 同时查看多个文件的大小

[root@rehat root]# du -h test1.txt test2.txt

9. 磁盘碎片整理

linux 下基本上不用碎片整理,它每隔一段时间会自动整理

10. 创建/改变文件系统

NO1. 创建文件系统类型

[root@rehat root]# umount /dev/sdb1

[root@rehat root]# mkfs -t ext3 /dev/db1

[root@rehat root]# mount /dev/sdb1 /practice

11. 改变文件或文件夹权限

chmod

NO1. 将自己的笔记设为只有自己才能看

[root@rehat root]# chmod go-rwx test.txt

或者

[root@rehat root]# chmod 700 test.txt

NO2. 同时修改多个文件的权限

[root@rehat root]# chmod 700 test1.txt test2.txt

NO3. 修改一个目录的权限,包括其子目录及文件

[root@rehat root]# chmod 700 -R test

12. 改变文件或文件夹拥有者

chown 该命令只有 root 才能使用

NO1. 更改某个文件的拥有者

[root@rehat root]# chown jim:usergroup test.txt

NO2. 更改某个目录的拥有者,并包含子目录

[root@rehat root]# chown jim:usergroup -R test

13. 查看文本文件内容

cat

NO1. 查看文件内容,并在每行前面加上行号

[root@rehat root]# cat -n test.txt

NO2. 查看文件内容,在不是空行的前面加上行号

[root@rehat root]# cat -b test.txt

NO3. 合并两个文件的内容

[root@rehat root]# cat test1.txt test2.txt > test_new.txt

NO4. 全并两具文件的内容,并追回到一个文件

[root@rehat root]# cat test1.txt test2.txt >> test_total.txt

NO5. 清空某个文件的内容

[root@rehat root]# cat /dev/null > test.txt

NO6. 创建一个新的文件

[root@rehat root]# cat > new.txt 按 CTRL + C 结束录入

14. 编辑文件文件

vi

NO1. 新建档案文件

[root@rehat root]# vi newfile.txt

NO2. 修改档案文件

[root@rehat root]# vi test.txt test.txt 已存在

NO3. vi 的两种工作模式:命令模式,编辑模式

NO4. 进入 vi 后为命令模式,按 Insrt 键进入编辑模式

按 ESC 进入命令模式,在命令模式不能编辑,只能输入命令

NO5. 命令模式常用命令

:w 保存当前文档

:q 直接退出 vi

:wq 先保存后退出

15. 路径操作

cd pwd

NO1. 显示当前路径

[root@rehat root]# pwd

NO2. 返回用户主目录

[root@rehat root]# cd

NO3. 改变到其它路径

[root@rehat root]# cd /etc

NO4. 返回到上一级目录

[root@rehat root]# cd ..

NO5. 返回到根目录

[root@rehat root]# cd /

16. 查询文件或文件夹

find

NO1. 查找当前用户主目录下的所有文件

[root@rehat root]# find ~

NO2. 让当前目录中文件属主具有读、写权限,并且文件所属组的用户和其他用户具有读权限的文件;

[root@rehat root]# find . -perm 644 -exec ls -l {} /;

NO3. 为了查找系统中所有文件长度为0的普通文件,并列出它们的完整路径;

[root@rehat root]# find / size 0 -type f -exec ls -l {} /;

NO4. 查找/var/logs目录中更改时间在7日以前的普通文件,并在删除之前询问它们;

[root@rehat root]# find /var/logs -mtime +7 -type f -ok rm -i {} /;

NO5. 为/找系统中所有属于root组的文件;

[root@rehat root]# find / -group root -exec ls -l {} /;

NO6. find命令将删除当目录中访问时间在7日以来、含有数字后缀的admin.log文件

[root@rehat root]# find . -name “admin.log[0-9][0-9][0-9]” -atime -7 -ok rm { } /;

NO7. 为了查找当前文件系统中的所有目录并排序

[root@rehat root]# find . -type d | sort

NO8. 为了查找系统中所有的rmt磁带设备

[root@rehat root]# find /dev/rmt

17. 显示文件/文件夹清单

ls / dir

NO1. 显示所有文件,包括以.开头的隐含文件

[root@rehat root]# ls -a

NO2. 显示文件的详细信息

[root@rehat root]# ls -l

NO3. 显示当前目录及所有子目录信息

[root@rehat root]# ls -Rl

NO4. 以时间排序显示目录,这在找最新文件有用

[root@rehat root]# ls -tl

NO5. 以文件大小排序

[root@rehat root]# ls -Sl

NO6. 显示文件大小,并按大小排序

[root@rehat root]# ls -s -l -S

18. 移动或更改文件/文件夹名称

mv 与 cp命令用法相似

NO1. 若移动目标文件已存在,要在移动之前,先备份原来的目录文件

[root@rehat root]# mv -b test.txt test2/

这样在 test2 下将有两个文件 test.txt 及 text.txt~

其中 test.txt~ 是备份文件,test.txt是新的文件

NO2. 若移动目标文件已存在,但不想弹出是否覆盖的提示,直接覆盖

[root@rehat root]# mv -f test.txt test2/

NO3. 当源与目标都拥有同一个文件,若源文件比目标新则移动,否则不移动

[root@rehat root]# mv -u test.txt test2/

NO4. 更改文件名称

[root@rehat root]# mv test.txt test2.txt

NO5. 更改目录名称

[root@rehat root]# mv /test2 /test2_2

<script type="text/javascript">printYo2FavControl("25856","http://czsilence.yo2.cn/articles/linux-cmd.html","linux实用命令详解"," alimama_pid=/"mm_10766414_900494_1846729/"; alimama_titlecolor=/"0000FF/"; alimama_descolor =/"000000/"; alimama_bgcolor=/"FFFFFF/"; alimama_bordercolor=/"E6E6E6/"; alimama_linkcolor=/"008000/"; alimama_bottomcolor=/"FFFFFF/"; alimama_anglesize=/"0");</script>
收藏:0 推荐:1
Tags: Linux,参考资料,命令.
04月 9, 2008

减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。

语法:gzip [选项] 压缩(解压缩)的文件名
各选项的含义:
-c 将输出写到标准输出上,并保留原有文件。
-d 将压缩文件解压。
-l 对每个压缩文件,显示下列字段:
压缩文件的大小
未压缩文件的大小
压缩比
未压缩文件的名字
-r 递归式地查找指定目录并压缩其中的所有文件或者是解压缩。
-t 测试,检查压缩文件是否完整。
-v 对每一个压缩和解压的文件,显示文件名和压缩比。
-num 用指定的数字num调整压缩的速度,-1或–fast表示最快压缩方法(低压缩比),-9或–best表示最慢压缩方法(高压缩比)。系统缺省值为6。

假设一个目录/home下有文件mm.txt、sort.txt、xx.com。
例1:把/home目录下的每个文件压缩成.gz文件。
$ cd /home
$ gzip *
$ ls
m.txt.gz sort.txt.gz xx.com.gz

例2:把例1中每个压缩的文件解压,并列出详细的信息。
$ gzip -dv *
mm.txt.gz 43.1%—–replaced with mm.txt
sort.txt.gz 43.1%—–replaced with sort.txt
xx.com.gz 43.1%—–replaced with xx.com
$ ls
mm.txt sort.txt xx.com

例3:详细显示例1中每个压缩的文件的信息,并不解压。
$ gzip -l *
compressed uncompr. ratio uncompressed_name
277 445 43.1% mm.txt
278 445 43.1% sort.txt
277 445 43.1% xx.com
$ ls
mm.txt.gz sort.txt.gz xx.com.gz

例4:压缩一个tar备份文件,如usr.tar,此时压缩文件的扩展名为.tar.gz
$ gzip usr.tar
$ ls
usr.tar.gz

<script type="text/javascript">printYo2FavControl("25632","http://czsilence.yo2.cn/articles/linux-gzip-cmd.html","linux gzip 命令详解","减少文件大小有两个明显的好处,一是可以减少存储空间,二是通过网络传输文件时,可以减少传输的时间。gzip是在Linux系统中经常使用的一个对文件进行压缩和解压缩的命令,既方便又好用。 语法:gzip [选项] 压缩(解压缩)的文件名 各选项的含义: -c 将输出写到标准输出上,并保留原有文件。 -d 将压缩文件解压。 -l 对每个压缩文件,显示下列字段: 压缩文件的大小 未压缩文件的大小 压缩比 未压缩文件的名字 -r 递归式地查找指定目录并压缩其中的所有文件或者是解压缩。 -t 测试,检查压缩");</script>
收藏:0 推荐:1
Tags: gzip,Linux,参考资料,命令,文件格式.
04月 8, 2008

原理:利用md5值的不同进行文件的对比。

操作背景

  1. XP安装光盘;
  2. 病毒样本;
  3. U盘;
  4. Ubuntu 7.10 LiveCD
  5. 所需的几个对比md5和转化二进制文件格式的程序

操作过程

1. 全盘格式化,同时安装Windows(也可采用ghost回去,但是一定注意其他磁盘可能的病毒感染)

2. 在刚装好的Windows下,导出注册表。将导出文件放入C盘根目录下。这里我命名为1.reg

3. 进入Ubuntu系统,注意,进入前f2选择简体中文模式

4. 挂载C盘:

mkdir /mnt/hdd1 (生产系统C盘挂载点)

mount -t ntfs -o iocharset=cp936 /dev/hdd1 /mnt/hdd1 (将系统C盘挂载到/mnt/hdd1下,注意文件格式和设备号视具体情况而定)

5. 挂载U盘:

mkdir /mnt/usb (生成U盘挂载点)

mount -t vfat /dev/sda1 /mnt/usb (将U盘挂载到/mnt/usb下,同样注意文件格式和设备号)

6. 将导出的注册表信息放入U盘:

假设U盘上已经有test目录,同时,在test目录下有parse.sh,parseWinReg,ShowList 三个程序

cp /mnt/hdd1/1.reg /mnt/usb/test (将导出注册表拷贝至/mnt/usb/test目录下)

cd /mnt/usb/test (进入U盘test 目录)

./parseWinReg 1.reg origreg (将导出注册表进行格式转换,生成origreg)

7. 计算C盘所有文件md5值:

rm /mnt/hdd1/pagefile.sys (这个文件太大影响计算速度,删除)

/mnt/usb/test/parse.sh /mnt/hdd1/ > /mnt/usb/origfile (计算磁盘文件md5值,并将结果导出至U盘test目录下origfile)

8. 重新进入Windows,同时,激发病毒文件

注意:先将病毒文件放入磁盘,拔掉U盘,拔掉网线,再激发!

9. 重复3,4,5,6,7步骤

mkdir /mnt/hdd1

mount -t ntfs -o iocharset=cp936 /dev/hdd1 /mnt/hdd1

mkdir /mnt/usb

mount -t vfat /dev/sda1 /mnt/usb

cp /mnt/hdd1/2.reg /mnt/usb/test (这里假设导出的注册表是2.reg)

cd /mnt/usb/test

./parseWinReg 2.reg newreg

rm /mnt/hdd1/pagefile.sys

/mnt/usb/test/parse.sh /mnt/hdd1/ > /mnt/usb/newfile

10. 至此,我们得到了原始的系统信息:origreg, origfile,中病毒之后的信息:newreg, newfile

11. 比较文件不同之处:diff -Nur origfile newfile > filediff

12. 比较注册表不同之处:diff -Nur origreg newreg > regdiff

13. 分析filediff 和 regdiff,得到结论

分析小技巧

一般情况下前面出现+的就是病毒释放的,-就是有过改动的(感染的),如果是md5值是成双成对出现(一个+和一个-),那那一行一般不是,如果前 面没有任何标记,那说明也不是。咱们把没用的删除,只留下有单个+或者单个-的,最好看文件路径,即得到了病毒的产生文件或者是感染文件。

<script type="text/javascript">printYo2FavControl("25614","http://czsilence.yo2.cn/articles/linux-virus-analysis.html","Linux操作系统下手动分析病毒样本","原理:利用md5值的不同进行文件的对比。 操作背景: XP安装光盘; 病毒样本; U盘; Ubuntu 7.10 LiveCD 所需的几个对比md5和转化二进制文件格式的程序 操作过程: 1. 全盘格式化,同时安装Windows(也可采用ghost回去,但是一定注意其他磁盘可能的病毒感染) 2. 在刚装好的Windows下,导出注册表。将导出文件放入C盘根目录下。这里我命名为1.reg 3. 进入Ubuntu系统,注意,进入前f2选择简体中文模式 4. 挂载C盘: mkdir /mnt/hd");</script>
收藏:0 推荐:2
Tags: Linux,操作系统,文件格式,病毒.
03月 21, 2008

Linux ext2/ext3文件系统使用索引节点来记录文件信息,作用像windows的文件分配表。索引节点是一个结构,它包含了一个文件的长度、创建及修改时 间、权限、所属关系、磁盘中的位置等信息。一个文件系统维护了一个索引节点的数组,每个文件或目录都与索引节点数组中的唯一一个元素对应。系统给每个索引 节点分配了一个号码,也就是该节点在数组中的索引号,称为索引节点号。 linux文件系统将文件索引节点号和文件名同时保存在目录中。所以,目录只是将文件的名称和它的索引节点号结合在一起的一张表,目录中每一对文件名称和 索引节点号称为一个连接。对于一个文件来说有唯一的索引节点号与之对应,对于一个索引节点号,却可以有多个文件名与之对应。因此,在磁盘上的同一个文件可 以通过不同的路径去访问它。

Linux缺省情况下使用的文件系统为Ext2,ext2文件系统的确高效稳定。但是,随着Linux系统在关键业务中的应用,Linux文件系统 的弱点也渐渐显露出来了:其中系统缺省使用的ext2文件系统是非日志文件系统。这在关键行业的应用是一个致命的弱点。本文向各位介绍Linux下使用 ext3日志文件系统应用。

Ext3文件系统是直接从Ext2文件系统发展而来,目前ext3文件系统已经非常稳定可靠。它完全兼容ext2文件系统。用户可以平滑地过渡到一个日志功能健全的文件系统中来。这实际上了也是ext3日志文件系统初始设计的初衷。

Ext3日志文件系统的特点:

1、高可用性

系统使用了ext3文件系统后,即使在非正常关机后,系统也不需要检查文件系统。宕机发生后,恢复ext3文件系统的时间只要数十秒钟。

2、数据的完整性:

ext3文件系统能够极大地提高文件系统的完整性,避免了意外宕机对文件系统的破坏。在保证数据完整性方面,ext3文件系统有2种模式可供选择。 其中之一就是“同时保持文件系统及数据的一致性”模式。采用这种方式,你永远不再会看到由于非正常关机而存储在磁盘上的垃圾文件。

3、文件系统的速度:

尽管使用ext3文件系统时,有时在存储数据时可能要多次写数据,但是,从总体上看来,ext3比ext2的性能还要好一些。这是因为ext3的日志功能对磁盘的驱动器读写头进行了优化。所以,文件系统的读写性能较之Ext2文件系统并来说,性能并没有降低。

4、数据转换

由ext2文件系统转换成ext3文件系统非常容易,只要简单地键入两条命令即可完成整个转换过程,用户不用花时间备份、恢复、格式化分区等。用一 个ext3文件系统提供的小工具tune2fs,它可以将ext2文件系统轻松转换为 ext3日志文件系统。另外,ext3文件系统可以不经任何更改,而直接加载成为ext2文件系统。

5、多种日志模式

Ext3有多种日志模式,一种工作模式是对所有的文件数据及metadata(定义文件系统中数据的数据,即数据的数据)进行日志记录(data= journal模式);另一种工作模式则是只对metadata记录日志,而不对数据进行日志记录,也即所谓data=ordered或者data= writeback模式。系统管理人员可以根据系统的实际工作要求,在系统的工作速度与文件数据的一致性之间作出选择。

实际使用Ext3文件系统

创建新的ext3文件系统,例如要把磁盘上的hda8分区格式化ext3文件系统,并将日志记录在/dev/hda1分区,那么操作过程如下:

[root@stationxx root]# mke2fs -j /dev/hda8
mke2fs 1.24a (02-Sep-2001)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
.. .. ..
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 30 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.

在创建新的文件系统时,可以看到,ext3文件系统执行自动检测的时间为180天或每第31次被mount时,实际上这个参数可以根据需要随意调节。

以下将新的文件系统mount到主分区/data目录下:

[root@stionxx root]# mount -t ext3 /dev/hda8 /data

说明:以上将已格式化为ext3文件系统的/dev/hda8分区加载到/data目录下。

ext3 基于ext2 的代码,它的磁盘格式和 ext2 的相同;这意味着,一个干净卸装的 ext3 文件系统可以作为 ext2 文件系统重新挂装。Ext3文件系统仍然能被加载成ext2文件系统来使用,你可以把一个文件系统在ext3和ext2自由切换。

这时在ext2文件系统上的ext3日志文件仍然存在,只是ext2不能认出日志而已。

将ext2文件系统转换为ext3文件系统

将linux系统的文件系统由ext2转至ext3,有以下几处优点:第一系统的可用性增强了,第二数据集成度提高,第三启动速度提高了,第四ext2与ext3文件系统之间相互转换容易。

以转换文件系统为例,将ext2文件系统转换为ext3文件系统,命令如下:

[root@stationxx root]# tune2fs -j /dev/hda9
tune2fs 1.24a (02-Sep-2001)
Creating journal inode: done
This filesystem will be automatically checked every 31 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.

这样,原来的ext2文件系统就转换成了ext3文件系统。注意将ext2文件系统转换为ext3文件系统时,不必要将分区缷载下来转换。

转换完成后,不要忘记将/etc/fstab文件中所对应分区的文件系统由原来的ext2更改为ext3。

ext3日志的存放位置

可以将日志放置在另外一个存储设备上,例如存放到分区/dev/hda8。例如要在/dev/hda8上创建一个ext3文件系统,并将日志存放在外部设备/dev/hda2上,则运行以下命令:

[root @stationxx root]#mke2fs -J device=/dev/hda8 /dev/hda2

ext3文件系统修复

新的e2fsprogs中的e2fsck支持ext3文件系统。当一个ext3文件系统被破坏时,先卸载该设备,再用e2fsck修复:

[root @stationxx root] # umount /dev/hda8

[root @stationxx root] #e2fsck -fy /dev/hda8

总而言之,ext3日志文件系统是目前linux系统由ext2文件系统过度到日志文件系统最为简单的一种选择,实现方式也最为简洁。由于是直接从 ext2文件系统发展而来,系统由ext2文件系统过渡到ext3日志文件系统升级过程平滑,可以最大限度地保证系统数据的安全性。目前linux系统要 使用日志文件系统,最保险的方式就是选择ext3文件系统。

<script type="text/javascript">printYo2FavControl("20665","http://czsilence.yo2.cn/articles/linux-ext2ext3%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e8%af%a6%e8%a7%a3.html","Linux ext2/ext3文件系统详解","Linux ext2/ext3文件系统使用索引节点来记录文件信息,作用像windows的文件分配表。索引节点是一个结构,它包含了一个文件的长度、创建及修改时 间、权限、所属关系、磁盘中的位置等信息。一个文件系统维护了一个索引节点的数组,每个文件或目录都与索引节点数组中的唯一一个元素对应。系统给每个索引 节点分配了一个号码,也就是该节点在数组中的索引号,称为索引节点号。 linux文件系统将文件索引节点号和文件名同时保存在目录中。所以,目录只是将文件的名称和它的索引节点号结合在一起的一张表,目录中");</script>
收藏:0 推荐:2
Tags: 操作系统.
03月 10, 2008

在使用Linux的过程中,大多初学者都遇到过系统启动到“grub>”提示符后就停止不前的问题,功夫不深的爱好者可能会束手无策,转而选择重装系统,其实这只是系统出现了一点小问题,既系统的GRUB配置文件出现了故障,此时可以采用以下两种方案来解决问题:

1)存在GRUB配置文件备份

如果存在GRUB配置文件备份,则可以比较简单的进行恢复,可以按如下方法进行:

使用光盘引导机器,进入到“linux rescue”模式,即系统维护模式;在提示符”sh#”下执行cp命令,将备份文件拷贝到/boot/grub.conf即可

Sh#cp /backup/grub.conf.bak /mnt/sysimage/boot/grub/grub.conf

假设备份文件为/backup/grub.conf.bak,因为大多数Linux光盘修复模式中会将硬盘系统的”/”分区chroot到 “/mnt/sysimage”处,所以拷贝的目标为“/mnt/sysimage/boot/grub/grub.conf”,而非 “/boot/grub/grub.conf”。

完成后重新启动即可。内核文件、镜像文件、/boot目录等文件损坏或丢失,也可以使用此种方法修复。

2)如果没有GRUB配置文件备份

如果没有备份文件时可以使用grub的交换功能,可以在”grub>”命令行下进行以下操作以手动启动系统:

查找/boot/grub/grub.conf分区所在的目录:

grub> find /boot/grub/grub.conf

(hd0,0)

需要注意的是,上述命令将得到的是grub.conf配置文件所在的系统的分区,(hd0,0)即表示它在分区hda1

查看grub.conf文件错误使用如下的命令:

grub> cat (hd0,0)/boot/grub/grub.conf

需要注意的是,可以查看配置文件到底什么地方出现了错误,以便进入后修改。

指定/boot分区使用命令:root (hd0,0)

从此步骤开始,即为本文前面提到的GRUB配置文件的主要引导步骤,只是一般都是系统读取GRUB配置文件,出现问题时我们可以使用手动启动。

指定内核加载的命令:kernel /boot/vmlinuz ro root=LABEL=/

指定镜像文件所在的位置可用如下命令:initrd /boot/initrd-2.6.24-1.3194.fc7

从/boot分区启动可以使用如下命令:boot (hd0,0)

此时系统即可正常启动,实际上以上步骤就是执行了GRUB引导期间加载grub.conf文件的步骤,当系统正常启动后将GRUB配置文件修改正确后即可。

<script type="text/javascript">printYo2FavControl("18464","http://czsilence.yo2.cn/articles/%e8%a7%a3%e6%9e%90linux%e7%b3%bb%e7%bb%9f%e4%b8%8bgrub%e6%95%85%e9%9a%9c%e4%bf%ae%e5%a4%8d.html","解析Linux系统下GRUB故障修复","在使用Linux的过程中,大多初学者都遇到过系统启动到“grub>”提示符后就停止不前的问题,功夫不深的爱好者可能会束手无策,转而选择重装系统,其实这只是系统出现了一点小问题,既系统的GRUB配置文件出现了故障,此时可以采用以下两种方案来解决问题: 1)存在GRUB配置文件备份 如果存在GRUB配置文件备份,则可以比较简单的进行恢复,可以按如下方法进行: 使用光盘引导机器,进入到“linux rescue”模式,即系统维护模式;在提示符”sh#”下执行cp命令,将备份文件拷贝到/boot/");</script>
收藏:0 推荐:2
Tags: GRUB,启动,操作系统.
02月 29, 2008

相 信用过Linux的朋友一定不会对vi陌生吧,它是Linux环境中使用最为普遍的全屏幕编文本辑器。但由于我们一般用户最初接触的都是微软的产品,它和 DOS下的EDIT和Windows下的记事本的使用方法不尽相似,所以另初学Linux朋友觉得vi很难用,甚至无从下手,下面我就向大家介绍vi的使 用方法。

首先需要说明的是,vi分为两种状态,即命令状态和编辑状态,在命令状态下,所键入的字符系统均作命令来处理,如:q代表退出,而编辑状态则是用来输入文字资料的。当你进入vi时,会首先进入命令状态。现在对vi也有了基本的认识,如果你有兴趣请接着往向看。

要进入vi,直接在系统提示符下键入vi <文件名>,当你键入的文件名是已有文件时,则系统自动打开此文件,否则将建立一个新文件。这时你将会看到屏幕左边会出现波浪线~,这就代表 该行是空的,没有任何文字,这时系统正在命令状态,怎样切换到编辑状态输入文字呢?按键盘上的Insert键即可,这时我们就可以像使用其它的编辑器一样 进行文字的编辑了,功能键也和其它编辑器差不多,下面就是一些功能键的说明,是不是很简单?

===========================================================
说明                功能键
===========================================================
移动光标到所在行的最前面      Home
移动光标到所在行的最后面      End
向下翻一页             Page Down
向上翻一页             Page Up
删除光标所在位置字符        Delete
删除光标所在位置前面的字符     Backspace
移动光标              ←↑↓→
===========================================================

另外,vi还支持粘贴与复制,不过用键盘来做实在很麻烦,现在哪台电脑没有鼠标呢?和Windows中一样,从你要复制的开始位置拖动鼠标到结束位 置,这块区域就反白了,再将光标移到你要粘贴的位置,按鼠标中键即可完成粘贴(如果你的鼠标是两个键的,同时按左右键即可模拟三键鼠标的中键),这比 Windos里方便吧。也许有的朋友会问,如何进行查找呢?下面我们按键盘上的Esc键切换到命令状态,输入“?<你要查找的字符串>”(不 带引号)就执行向下查找操作,而“/<你要查找的字符串>”表示向上查找,键盘上的n表示重复一次,而N表示反方向重复一次。

说了半天,可能你的文件已经编辑完成了,但如何存盘呢?现在我们还是保持在命令状态,按:w按后回车即完成了存盘工作,而退出vi返回到Linux的命令是:q,这两个命令也可以组合使用,如:wq代表存盘退出。

好了,说了半天,可能你的头已经大了,上面讲到的那些只是vi中最常用的功能,至于其它的功能你可以在使用中慢慢体会。顺便说一句,不要强记那些命令,它们看起来多而毫无规律,多用几次自然就熟练了,现在我觉得比Windows中的记事本方便多了。

<script type="text/javascript">printYo2FavControl("13841","http://czsilence.yo2.cn/articles/vi%e4%bd%bf%e7%94%a8%e6%8c%87%e5%8d%97.html","vi使用指南","相信用过Linux的朋友一定不会对vi陌生吧,它是Linux环境中使用最为普遍的全屏幕编文本辑器。但由于我们一般用户最初接触的都是微软的产品,它和DOS下的EDIT和Windows下的记事本的使用方法不尽相似,所以另初学Linux朋友觉得vi很难用,甚至无从下手,下面我就向大家介绍vi的使用方法。 首先需要说明的是,vi分为两种状态,即命令状态和编辑状态,在命令状态下,所键入的字符系统均作命令来处理,如:q代表退出,而编辑状态则是用来输入文字资料的。当你进入vi时,会首先进入命令状态。现在对vi");</script>
收藏:0 推荐:0
Tags: vi,教程.
02月 28, 2008

一.运行 gcc/egcs

Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC能够编译三种语言:C、C++和ObjectC(C语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。
#DEMO#: hello.c

如果你有两个或少数几个C源文件,也可以方便地利用GCC编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。

清单 factorial.c

#include
#include

int factorial (int n)
{
if (n <= 1)
return 1;

else
return factorial (n - 1) * n;
}

清单 main.c

#include
#include

int factorial (int n);

int main (int argc, char **argv)
{
int n;

if (argc < 2) {
printf (”Usage: %s n “, argv [0]);
return -1;
}
else {
n = atoi (argv[1]);
printf (”Factorial of %d is %d. “, n, factorial (n));
}

return 0;
}

利用如下的命令可编译生成可执行文件,并执行程序:

$ gcc -o factorial main.c factorial.c

$ ./factorial 5

Factorial of 5 is 120.

GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。

但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。

假设我们有一个如下的 C++ 源文件(hello.C):

#include

void main (void)
{
cout << “Hello, world!” << endl;
}

则可以如下调用 g++ 命令编译、连接并生成可执行文件:

$ g++ -o hello hello.C

$ ./hello

Hello, world!

二.gcc/egcs 的主要选项

-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,例如 asm 或 typeof 关键词。

-c 只编译并生成目标文件。

-DMACRO 以字符串“1”定义 MACRO 宏。

-DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。

-E 只运行 C 预编译器。

-g 生成调试信息。GNU 调试器可利用该信息。

-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。

-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。

-lLIBRARY 连接时搜索指定的函数库LIBRARY。

-m486 针对 486 进行代码优化。

-o FILE 生成指定的输出文件。用在生成可执行文件时。

-O0 不进行优化处理。

-O 或 -O1 优化生成代码。

-O2 进一步优化。

-O3 比 -O2 更进一步优化,包括 inline 函数。

-shared 生成共享目标文件。通常用在建立共享库时。

-static 禁止使用共享连接。

-UMACRO 取消对 MACRO 宏的定义。

-w 不生成任何警告信息。

-Wall 生成所有警告信息。

<script type="text/javascript">printYo2FavControl("13824","http://czsilence.yo2.cn/articles/linux%e4%b8%8a%e7%9a%84cc%e7%bc%96%e8%af%91%e5%99%a8gccegcs%e8%af%a6%e8%a7%a3.html","Linux上的C/C++编译器gcc/egcs详解","一.运行 gcc/egcs Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC能够编译三种语言:C、C++和ObjectC(C语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。 #DEMO#: hello.c 如果你有两个或少数几个C源文件,也可以方便地利用GCC编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。");</script>
收藏:0 推荐:0
Tags: GCC,编译器.
02月 25, 2008

进入vi的命令
vi filename :打开或新建文件,并将光标置于第一行首
vi +n filename :打开文件,并将光标置于第n行首
vi + filename :打开文件,并将光标置于最后一行首
vi +/pattern filename:打开文件,并将光标置于第一个与pattern匹配的串处
vi -r filename :在上次正用vi编辑时发生系统崩溃,恢复filename
vi filename….filename :打开多个文件,依次编辑

移动光标类命令
h :光标左移一个字符
l :光标右移一个字符
space:光标右移一个字符
Backspace:光标左移一个字符
k或Ctrl+p:光标上移一行
j或Ctrl+n :光标下移一行
Enter :光标下移一行
w或W :光标右移一个字至字首
b或B :光标左移一个字至字首
e或E :光标右移一个字j至字尾
) :光标移至句尾
( :光标移至句首
}:光标移至段落开头
{:光标移至段落结尾
nG:光标移至第n行首
n+:光标下移n行
n-:光标上移n行
n$:光标移至第n行尾
H :光标移至屏幕顶行
M :光标移至屏幕中间行
L :光标移至屏幕最后行
0:(注意是数字零)光标移至当前行首
$:光标移至当前行尾

屏幕翻滚类命令
Ctrl+u:向文件首翻半屏
Ctrl+d:向文件尾翻半屏
Ctrl+f:向文件尾翻一屏
Ctrl+b;向文件首翻一屏
nz:将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

插入文本类命令
i :在光标前
I :在当前行首
a:光标后
A:在当前行尾
o:在当前行之下新开一行
O:在当前行之上新开一行
r:替换当前字符
R:替换当前字符及其后的字符,直至按ESC键
s:从当前光标位置处开始,以输入的文本替代指定数目的字符
S:删除指定数目的行,并以所输入文本代替之
ncw或nCW:修改指定数目的字
nCC:修改指定数目的行

删除命令
ndw或ndW:删除光标处开始及其后的n-1个字
do:删至行首
d$:删至行尾
ndd:删除当前行及其后n-1行
x或X:删除一个字符,x删除光标后的,而X删除光标前的
Ctrl+u:删除输入方式下所输入的文本

搜索及替换命令

/pattern:从光标开始处向文件尾搜索pattern
?pattern:从光标开始处向文件首搜索pattern
n:在同一方向重复上一次搜索命令
N:在反方向上重复上一次搜索命令
:s/p1/p2/g:将当前行中所有p1均用p2替代
:n1,n2s/p1/p2/g:将第n1至n2行中所有p1均用p2替代
:g/p1/s//p2/g:将文件中所有p1均用p2替换

选项设置
all:列出所有选项设置情况
term:设置终端类型
ignorance:在搜索中忽略大小写
list:显示制表位(Ctrl+I)和行尾标志($)
number:显示行号
report:显示由面向行的命令修改过的数目
terse:显示简短的警告信息
warn:在转到别的文件时若没保存当前文件则显示NO write信息
nomagic:允许在搜索模式中,使用前面不带“/”的特殊字符
nowrapscan:禁止vi在搜索到达文件两端时,又从另一端开始
mesg:允许vi显示其他用户用write写到自己终端上的信息

最后行方式命令
:n1,n2 co n3:将n1行到n2行之间的内容拷贝到第n3行下
:n1,n2 m n3:将n1行到n2行之间的内容移至到第n3行下
:n1,n2 d :将n1行到n2行之间的内容删除
:w :保存当前文件
:e filename:打开文件filename进行编辑
:x:保存当前文件并退出
:q:退出vi
:q!:不保存文件并退出vi
:!command:执行shell命令command
:n1,n2 w!command:将文件中n1行至n2行的内容作为command的输入并执行之,若不指
定n1,n2,则表示将整个文件内容作为command的输入
:r!command:将命令command的输出结果放到当前行 。

<script type="text/javascript">printYo2FavControl("13853","http://czsilence.yo2.cn/articles/vi%e5%91%bd%e4%bb%a4%e6%89%8b%e5%86%8c.html","VI命令手册","进入vi的命令 vi filename :打开或新建文件,并将光标置于第一行首 vi +n filename :打开文件,并将光标置于第n行首 vi + filename :打开文件,并将光标置于最后一行首 vi +/pattern filename:打开文件,并将光标置于第一个与pattern匹配的串处 vi -r filename :在上次正用vi编辑时发生系统崩溃,恢复filename vi filename….filename :打开多个文件,依次编辑 移动光标类命令 h :光标左移一个");</script>
收藏:0 推荐:0
Tags: vi,参考资料.
02月 24, 2008

相 对于其它文件类型,可执行文件可能是一个操作系统中最重要的文件类型,因为它们是完成操作的真正执行者。可执行文件的大小、运行速度、资源占用情况以及可 扩展性、可移植性等与文件格式的定义和文件加载过程紧密相关。研究可执行文件的格式对编写高性能程序和一些黑客技术的运用都是非常有意义的。
不管何种可执行文件格式,一些基本的要素是必须的,显而易见的,文件中应包含代码和数据。因为文件可能引用外部文件定义的符号(变量和函数),因此重定位 信息和符号信息也是需要的。一些辅助信息是可选的,如调试信息、硬件信息等。基本上任意一种可执行文件格式都是按区间保存上述信息,称为段 (Segment)或节(Section)。不同的文件格式中段和节的含义可能有细微区别,但根据上下文关系可以很清楚的理解,这不是关键问题。最后,可 执行文件通常都有一个文件头部以描述本文件的总体结构。
相对可执行文件有三个重要的概念:编译(compile)、连接(link,也可称为链接、联接)、加载(load)。源程序文件被编译成目标文件,多个 目标文件被连接成一个最终的可执行文件,可执行文件被加载到内存中运行。因为本文重点是讨论可执行文件格式,因此加载过程也相对重点讨论。下面是 LINUX平台下ELF文件加载过程的一个简单描述。
1:内核首先读ELF文件的头部,然后根据头部的数据指示分别读入各种数据结构,找到标记为可加载(loadable)的段,并调用函数 mmap()把段内容加载到内存中。在加载之前,内核把段的标记直接传递给 mmap(),段的标记指示该段在内存中是否可读、可写,可执行。显然,文本段是只读可执行,而数据段是可读可写。这种方式是利用了现代操作系统和处理器 对内存的保护功能。著名的Shellcode(参考资料 17)的编写技巧则是突破此保护功能的一个实际例子。
2:内核分析出ELF文件标记为 PT_INTERP 的段中所对应的动态连接器名称,并加载动态连接器。现代 LINUX 系统的动态连接器通常是 /lib/ld-linux.so.2,相关细节在后面有详细描述。
3:内核在新进程的堆栈中设置一些标记-值对,以指示动态连接器的相关操作。
4:内核把控制传递给动态连接器。
5:动态连接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行加载。
6:动态连接器对程序的外部引用进行重定位,通俗的讲,就是告诉程序其引用的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内。动态连接还有一个延迟(Lazy)定位的特性,即只在”真正”需要引用符号时才重定位,这对提高程序运行效率有极大帮助。
7:动态连接器执行在ELF文件中标记为 .init 的节的代码,进行程序运行的初始化。在早期系统中,初始化代码对应函数 _init(void)(函数名强制固定),在现代系统中,则对应形式为

void
__attribute((constructor))
init_function(void)
{
……
}
其中函数名为任意。
8:动态连接器把控制传递给程序,从 ELF 文件头部中定义的程序进入点开始执行。在 a.out 格式和ELF格式中,程序进入点的值是显式存在的,在 COFF 格式中则是由规范隐含定义。
从上面的描述可以看出,加载文件最重要的是完成两件事情:加载程序段和数据段到内存;进行外部定义符号的重定位。重定位是程序连接中一个重要概念。我们知 道,一个可执行程序通常是由一个含有 main() 的主程序文件、若干目标文件、若干共享库(Shared Libraries)组成。(注:采用一些特别的技巧,也可编写没有 main 函数的程序,请参阅参考资料 2)一个 C 程序可能引用共享库定义的变量或函数,换句话说就是程序运行时必须知道这些变量/函数的地址。在静态连接中,程序所有需要使用的外部定义都完全包含在可执 行程序中,而动态连接则只在可执行文件中设置相关外部定义的一些引用信息,真正的重定位是在程序运行之时。静态连接方式有两个大问题:如果库中变量或函数 有任何变化都必须重新编译连接程序;如果多个程序引用同样的变量/函数,则此变量/函数会在文件/内存中出现多次,浪费硬盘/内存空间。比较两种连接方式 生成的可执行文件的大小,可以看出有明显的区别。
a.out 文件格式分析
a.out 格式在不同的机器平台和不同的 UNIX 操作系统上有轻微的不同,例如在 MC680×0 平台上有 6 个 section。下面我们讨论的是最”标准”的格式。
a.out 文件包含 7 个 section,格式如下:
exec header(执行头部,也可理解为文件头部)
text segment(文本段)
data segment(数据段)
text relocations(文本重定位段)
data relocations(数据重定位段)
symbol table(符号表)
string table(字符串表)
执行头部的数据结构:

struct exec {
unsigned long a_midmag; /* 魔数和其它信息 */
unsigned long a_text; /* 文本段的长度 */
unsigned long a_data; /* 数据段的长度 */
unsigned long a_bss; /* BSS段的长度 */
unsigned long a_syms; /* 符号表的长度 */
unsigned long a_entry; /* 程序进入点 */
unsigned long a_trsize; /* 文本重定位表的长度 */
unsigned long a_drsize; /* 数据重定位表的长度 */
};
文件头部主要描述了各个 section 的长度,比较重要的字段是 a_entry(程序进入点),代表了系统在加载程序并初试化各种环境后开始执行程序代码的入口。这个字段在后面讨论的 ELF 文件头部中也有出现。由 a.out 格式和头部数据结构我们可以看出,a.out 的格式非常紧凑,只包含了程序运行所必须的信息(文本、数据、BSS),而且每个 section 的顺序是固定的。这种结构缺乏扩展性,如不能包含”现代”可执行文件中常见的调试信息,最初的 UNIX 黑客对 a.out 文件调试使用的工具是 adb,而 adb 是一种机器语言调试器!
a.out 文件中包含符号表和两个重定位表,这三个表的内容在连接目标文件以生成可执行文件时起作用。在最终可执行的 a.out 文件中,这三个表的长度都为 0。a.out 文件在连接时就把所有外部定义包含在可执行程序中,如果从程序设计的角度来看,这是一种硬编码方式,或者可称为模块之间是强藕和的。在后面的讨论中,我们 将会具体看到ELF格式和动态连接机制是如何对此进行改进的。
a.out 是早期UNIX系统使用的可执行文件格式,由 AT&T 设计,现在基本上已被 ELF 文件格式代替。a.out 的设计比较简单,但其设计思想明显的被后续的可执行文件格式所继承和发扬。可以参阅参考资料 16 和阅读参考资料 15 源代码加深对 a.out 格式的理解。参考资料 12 讨论了如何在”现代”的红帽LINUX运行 a.out 格式文件。
COFF 文件格式分析
COFF 格式比 a.out 格式要复杂一些,最重要的是包含一个节段表(section table),因此除了 .text,.data,和 .bss 区段以外,还可以包含其它的区段。另外也多了一个可选的头部,不同的操作系统可一对此头部做特定的定义。
COFF 文件格式如下:
File Header(文件头部)
Optional Header(可选文件头部)
Section 1 Header(节头部)
………
Section n Header(节头部)
Raw Data for Section 1(节数据)
Raw Data for Section n(节数据)
Relocation Info for Sect. 1(节重定位数据)
Relocation Info for Sect. n(节重定位数据)
Line Numbers for Sect. 1(节行号数据)
Line Numbers for Sect. n(节行号数据)
Symbol table(符号表)
String table(字符串表)
文件头部的数据结构:

struct filehdr
{
unsigned short f_magic; /* 魔数 */
unsigned short f_nscns; /* 节个数 */
long f_timdat; /* 文件建立时间 */
long f_symptr; /* 符号表相对文件的偏移量 */
long f_nsyms; /* 符号表条目个数 */
unsigned short f_opthdr; /* 可选头部长度 */
unsigned short f_flags; /* 标志 */
};
COFF 文件头部中魔数与其它两种格式的意义不太一样,它是表示针对的机器类型,例如 0×014c 相对于 I386 平台,而 0×268 相对于 Motorola 68000系列等。当 COFF 文件为可执行文件时,字段 f_flags 的值为 F_EXEC(0X00002),同时也表示此文件没有未解析的符号,换句话说,也就是重定位在连接时就已经完成。由此也可以看出,原始的 COFF 格式不支持动态连接。为了解决这个问题以及增加一些新的特性,一些操作系统对 COFF 格式进行了扩展。Microsoft 设计了名为 PE(Portable Executable)的文件格式,主要扩展是在 COFF 文件头部之上增加了一些专用头部,具体细节请参阅参考资料 18,某些 UNIX 系统也对 COFF 格式进行了扩展,如 XCOFF(extended common object file format)格式,支持动态连接,请参阅参考资料 5。
紧接文件头部的是可选头部,COFF 文件格式规范中规定可选头部的长度可以为 0,但在 LINUX 系统下可选头部是必须存在的。下面是 LINUX 下可选头部的数据结构:

typedef struct
{
char magic[2]; /* 魔数 */
char vstamp[2]; /* 版本号 */
char tsize[4]; /* 文本段长度 */
char dsize[4]; /* 已初始化数据段长度 */
char bsize[4]; /* 未初始化数据段长度 */
char entry[4]; /* 程序进入点 */
char text_start[4]; /* 文本段基地址 */
char data_start[4]; /* 数据段基地址 */
}
COFF_AOUTHDR;
字段 magic 为 0413 时表示 COFF 文件是可执行的,注意到可选头部中显式定义了程序进入点,标准的 COFF 文件没有明确的定义程序进入点的值,通常是从 .text 节开始执行,但这种设计并不好。
前面我们提到,COFF 格式比 a.out 格式多了一个节段表,一个节头条目描述一个节数据的细节,因此 COFF 格式能包含更多的节,或者说可以根据实际需要,增加特定的节,具体表现在 COFF 格式本身的定义以及稍早提及的 COFF 格式扩展。我个人认为,节段表的出现可能是 COFF 格式相对 a.out 格式最大的进步。下面我们将简单描述 COFF 文件中节的数据结构,因为节的意义更多体现在程序的编译和连接上,所以本文不对其做更多的描述。此外,ELF 格式和 COFF格式对节的定义非常相似,在随后的 ELF 格式分析中,我们将省略相关讨论。

struct COFF_scnhdr
{
char s_name[8]; /* 节名称 */
char s_paddr[4]; /* 物理地址 */
char s_vaddr[4]; /* 虚拟地址 */
char s_size[4]; /* 节长度 */
char s_scnptr[4]; /* 节数据相对文件的偏移量 */
char s_relptr[4]; /* 节重定位信息偏移量 */
char s_lnnoptr[4]; /* 节行信息偏移量 */
char s_nreloc[2]; /* 节重定位条目数 */
char s_nlnno[2]; /* 节行信息条目数 */
char s_flags[4]; /* 段标记 */
};
有一点需要注意:LINUX系统中头文件coff.h中对字段 s_paddr的注释是”physical address”,但似乎应该理解为”节被加载到内存中所占用的空间长度”。字段s_flags标记该节的类型,如文本段、数据段、BSS段等。在 COFF的节中也出现了行信息,行信息描述了二进制代码与源代码的行号之间的对映关系,在调试时很有用。
参考资料 19是一份对COFF格式详细描述的中文资料,更详细的内容请参阅参考资料 20。
ELF文件格式分析
ELF文件有三种类型:可重定位文件:也就是通常称的目标文件,后缀为.o。共享文件:也就是通常称的库文件,后缀为.so。可执行文件:本文主要讨论的 文件格式,总的来说,可执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)。
首先看看ELF文件的总体布局:
ELF header(ELF头部)
Program header table(程序头表)
Segment1(段1)
Segment2(段2)
………
Sengmentn(段n)
Setion header table(节头表,可选)
段由若干个节(Section)构成,节头表对每一个节的信息有相关描述。对可执行程序而言,节头表是可选的。参考资料 1中作者谈到把节头表的所有数据全部设置为0,程序也能正确运行!ELF头部是一个关于本文件的路线图(road map),从总体上描述文件的结构。下面是ELF头部的数据结构:

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* 魔数和相关信息 */
Elf32_Half e_type; /* 目标文件类型 */
Elf32_Half e_machine; /* 硬件体系 */
Elf32_Word e_version; /* 目标文件版本 */
Elf32_Addr e_entry; /* 程序进入点 */
Elf32_Off e_phoff; /* 程序头部偏移量 */
Elf32_Off e_shoff; /* 节头部偏移量 */
Elf32_Word e_flags; /* 处理器特定标志 */
Elf32_Half e_ehsize; /* ELF头部长度 */
Elf32_Half e_phentsize; /* 程序头部中一个条目的长度 */
Elf32_Half e_phnum; /* 程序头部条目个数 */
Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */
Elf32_Half e_shnum; /* 节头部条目个数 */
Elf32_Half e_shstrndx; /* 节头部字符表索引 */
} Elf32_Ehdr;
下面我们对ELF头表中一些重要的字段作出相关说明,完整的ELF定义请参阅参考资料 6和参考资料7。
e_ident[0]-e_ident[3]包含了ELF文件的魔数,依次是0×7f、’E'、’L'、’F'。注意,任何一个ELF文件必须包含此魔 数。参考资料 3中讨论了利用程序、工具、/Proc文件系统等多种查看ELF魔数的方法。e_ident[4]表示硬件系统的位数,1代表32位,2代表64位。 e_ident[5]表示数据编码方式,1代表小印第安排序(最大有意义的字节占有最低的地址),2代表大印第安排序(最大有意义的字节占有最高的地 址)。e_ident[6]指定ELF头部的版本,当前必须为1。e_ident[7]到e_ident[14]是填充符,通常是0。ELF格式规范中定 义这几个字节是被忽略的,但实际上是这几个字节完全可以可被利用。如病毒Lin/Glaurung.676/666(参考资料 1)设置e_ident[7]为0×21,表示本文件已被感染;或者存放可执行代码(参考资料 2)。ELF头部中大多数字段都是对子头部数据的描述,其意义相对比较简单。值得注意的是某些病毒可能修改字段e_entry(程序进入点)的值,以指向 病毒代码,例如上面提到的病毒Lin/Glaurung.676/666。
一个实际可执行文件的文件头部形式如下:(利用命令readelf)

ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0×1
Entry point address: 0×80483cc
Start of program headers: 52 (bytes into file)
Start of section headers: 14936 (bytes into file)
Flags: 0×0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31

紧接ELF头部的是程序头表,它是一个结构数组,包含了ELF头表中字段e_phnum定义的条目,结构描述一个段或其他系统准备执行该程序所需要的信息。

typedef struct {
Elf32_Word p_type; /* 段类型 */
Elf32_Off p_offset; /* 段位置相对于文件开始处的偏移量 */
Elf32_Addr p_vaddr; /* 段在内存中的地址 */
Elf32_Addr p_paddr; /* 段的物理地址 */
Elf32_Word p_filesz; /* 段在文件中的长度 */
Elf32_Word p_memsz; /* 段在内存中的长度 */
Elf32_Word p_flags; /* 段的标记 */
Elf32_Word p_align; /* 段在内存中对齐标记 */
} Elf32_Phdr;
在详细讨论可执行文件程序头表之前,首先查看一个实际文件的输出:

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0×000034 0×08048034 0×08048034 0×000c0 0×000c0 R E 0×4
INTERP 0×0000f4 0×080480f4 0×080480f4 0×00013 0×00013 R 0×1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0×000000 0×08048000 0×08048000 0×00684 0×00684 R E 0×1000
LOAD 0×000684 0×08049684 0×08049684 0×00118 0×00130 RW 0×1000
DYNAMIC 0×000690 0×08049690 0×08049690 0×000c8 0×000c8 RW 0×4
NOTE 0×000108 0×08048108 0×08048108 0×00020 0×00020 R 0×4
Section to Segment mapping:
Segment Sections…
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt
.init .plt .text .fini .rodata .eh_frame
03 .data .dynamic .ctors .dtors .jcr .got .bss
04 .dynamic
05 .note.ABI-tag
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 080480f4 0000f4 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048108 000108 000020 00 A 0 0 4
[ 3] .hash HASH 08048128 000128 000040 04 A 4 0 4
[ 4] .dynsym DYNSYM 08048168 000168 0000b0 10 A 5 1 4
[ 5] .dynstr STRTAB 08048218 000218 00007b 00 A 0 0 1
[ 6] .gnu.version VERSYM 08048294 000294 000016 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 080482ac 0002ac 000030 00 A 5 1 4
[ 8] .rel.dyn REL 080482dc 0002dc 000008 08 A 4 0 4
[ 9] .rel.plt REL 080482e4 0002e4 000040 08 A 4 b 4
[10] .init PROGBITS 08048324 000324 000017 00 AX 0 0 4
[11] .plt PROGBITS 0804833c 00033c 000090 04 AX 0 0 4
[12] .text PROGBITS 080483cc 0003cc 0001f8 00 AX 0 0 4
[13] .fini PROGBITS 080485c4 0005c4 00001b 00 AX 0 0 4
[14] .rodata PROGBITS 080485e0 0005e0 00009f 00 A 0 0 32
[15] .eh_frame PROGBITS 08048680 000680 000004 00 A 0 0 4
[16] .data PROGBITS 08049684 000684 00000c 00 WA 0 0 4
[17] .dynamic DYNAMIC 08049690 000690 0000c8 08 WA 5 0 4
[18] .ctors PROGBITS 08049758 000758 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049760 000760 000008 00 WA 0 0 4
[20] .jcr PROGBITS 08049768 000768 000004 00 WA 0 0 4
[21] .got PROGBITS 0804976c 00076c 000030 04 WA 0 0 4
[22] .bss NOBITS 0804979c 00079c 000018 00 WA 0 0 4
[23] .comment PROGBITS 00000000 00079c 000132 00 0 0 1
[24] .debug_aranges PROGBITS 00000000 0008d0 000098 00 0 0 8
[25] .debug_pubnames PROGBITS 00000000 000968 000040 00 0 0 1
[26] .debug_info PROGBITS 00000000 0009a8 001cc6 00 0 0 1
[27] .debug_abbrev PROGBITS 00000000 00266e 0002cc 00 0 0 1
[28] .debug_line PROGBITS 00000000 00293a 0003dc 00 0 0 1
[29] .debug_frame PROGBITS 00000000 002d18 000048 00 0 0 4
[30] .debug_str PROGBITS 00000000 002d60 000bcd 01 MS 0 0 1
[31] .shstrtab STRTAB 00000000 00392d 00012b 00 0 0 1
[32] .symtab SYMTAB 00000000 003fa8 000740 10 33 56 4
[33] .strtab STRTAB 00000000 0046e8 000467 00 0 0 1

对一个ELF可执行程序而言,一个基本的段是标记p_type为PT_INTERP的段,它表明了运行此程序所需要的程序解释器(/lib/ld- linux.so.2),实际上也就是动态连接器(dynamic linker)。最重要的段是标记p_type为PT_LOAD的段,它表明了为运行程序而需要加载到内存的数据。查看上面实际输入,可以看见有两个可 LOAD段,第一个为只读可执行(FLg为R E),第二个为可读可写(Flg为RW)。段1包含了文本节.text,注意到ELF文件头部中程序进入点的值为0×80483cc,正好是指向节. text在内存中的地址。段二包含了数据节.data,此数据节中数据是可读可写的,相对的只读数据节.rodata包含在段1中。ELF格式可以比 COFF格式包含更多的调试信息,如上面所列出的形式为.debug_xxx的节。在I386平台LINUX系统下,用命令file查看一个ELF可执行 程序的可能输出是:a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped。
ELF文件中包含了动态连接器的全路径,内核定位”正确”的动态连接器在内存中的地址是”正确”运行可执行文件的保证,参考资料 13讨论了如何通过查找动态连接器在内存中的地址以达到颠覆(Subversiver)动态连接机制的方法。
最后我们讨论ELF文件的动态连接机制。每一个外部定义的符号在全局偏移表(Global Offset Table GOT)中有相应的条目,如果符号是函数则在过程连接表(Procedure Linkage Table PLT)中也有相应的条目,且一个PLT条目对应一个GOT条目。对外部定义函数解析可能是整个ELF文件规范中最复杂的,下面是函数符号解析过程的一个 描述。
1:代码中调用外部函数func,语句形式为call 0xaabbccdd,地址0xaabbccdd实际上就是符号func在PLT表中对应的条目地址(假设地址为标号.PLT2)。
2:PLT表的形式如下

.PLT0: pushl 4(%ebx) /* GOT表的地址保存在寄存器ebx中 */
jmp *8(%ebx)
nop; nop
nop; nop
.PLT1: jmp *name1@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
.PLT2: jmp *func@GOT(%ebx)
pushl $offset
jmp .PLT0@PC

3:查看标号.PLT2的语句,实际上是跳转到符号func在GOT表中对应的条目。
4:在符号没有重定位前,GOT表中此符号对应的地址为标号.PLT2的下一条语句,即是pushl $offset,其中$offset是符号func的重定位偏移量。注意到这是一个二次跳转。
5:在符号func的重定位偏移量压栈后,控制跳到PLT表的第一条目,把GOT[1]的内容压栈,并跳转到GOT[2]对应的地址。
6:GOT[2]对应的实际上是动态符号解析函数的代码,在对符号func的地址解析后,会把func在内存中的地址设置到GOT表中此符号对应的条目中。
7:当第二次调用此符号时,GOT表中对应的条目已经包含了此符号的地址,就可直接调用而不需要利用PLT表进行跳转。
动态连接是比较复杂的,但为了获得灵活性的代价通常就是复杂性。其最终目的是把GOT表中条目的值修改为符号的真实地址,这也可解释节.got包含在可读可写段中。
动态连接是一个非常重要的进步,这意味着库文件可以被升级、移动到其他目录等等而不需要重新编译程序(当然,这不意味库可以任意修改,如函数入参的个数、 数据类型应保持兼容性)。从很大程度上说,动态连接机制是ELF格式代替a.out格式的决定性原因。如果说面对对象的编程本质是面对接口 (interface)的编程,那么动态连接机制则是这种思想的地一个非常典型的应用,具体的讲,动态连接机制与设计模式中的桥接(BRIDGE)方法比 较类似,而它的LAZY特性则与代理(PROXY)方法非常相似。动态连接操作的细节描述请参阅参考资料 8,9,10,11。通过阅读命令readelf、objdump 的源代码以及参考资料 14中所提及的相关软件源代码,可以对ELF文件的格式有更彻底的了解。
总结
不同时期的可执行文件格式深刻的反映了技术进步的过程,技术进步通常是针对解决存在的问题和适应新的环境。早期的UNIX系统使用a.out格式,随着操 作系统和硬件系统的进步,a.out格式的局限性越来越明显。新的可执行文件格式COFF在UNIX System VR3中出现,COFF格式相对a.out格式最大变化是多了一个节头表(section head table),能够在包含基础的文本段、数据段、BSS段之外包含更多的段,但是COFF对动态连接和C++程序的支持仍然比较困难。为了解决上述问题, UNIX系统实验室(UNIX SYSTEM Laboratories USL) 开发出ELF文件格式,它被作为应用程序二进制接口(Application binary Interface ABI)的一部分,其目的是替代传统的a.out格式。例如,ELF文件格式中引入初始化段.init和结束段.fini(分别对应构造函数和析构函数) 则主要是为了支持C++程序。1994年6月ELF格式出现在LINUX系统上,现在ELF格式作为UNIX/LINUX最主要的可执行文件格式。当然我 们完全有理由相信,在将来还会有新的可执行文件格式出现。
上述三种可执行文件格式都很好的体现了设计思想中分层的概念,由一个总的头部刻画了文件的基本要素,再由若干子头部/条目刻画了文件的若干细节。比较一下 可执行文件格式和以太数据包中以太头、IP头、TCP头的设计,我想我们能很好的感受分层这一重要的设计思想。参考资料 21从全局的角度讨论了各种文件的格式,并提出一个比较夸张的结论:Everything Is Byte!
最后的题外话:大多数资料中对a.out格式的评价较低,常见的词语有黑暗年代(dark ages)、丑陋(ugly)等等,当然,从现代的观点来看,的确是比较简单,但是如果没有曾经的简单何来今天的精巧?正如我们今天可以评价石器时代的技 术是ugly,那么将来的人们也可以嘲讽今天的技术是非常ugly。我想我们也许应该用更平和的心态来对曾经的技术有一个公正的评价。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值