标签 GCC 下的文章

在Linux上工作

Linux上学习Linux内核我想应该是最好的方法了。Linux对我来说绝对是一个新鲜环境,搭建在Linux上的工作环境就是我的首要工作,这篇blog记录的就是我在Linux上的工作环境,也希望对大家有些借鉴意义。

我的Linux是在一个多月以前安装的[注1],安装的版本是Fedora Core 4。我使用的是本地磁盘映像安装,磁盘映像文件很大,总共4个,大约2.4G体积。安装过程倒是没有像网上很多人说得那样不顺利,包括修改、合并分区在内大约用了3个小时就看到Linux的桌面了。

进入Linux首先映入眼帘的的就是Linux桌面,我选择了GNOME(GNU Network Object Model Environment)桌面,不为什么,就是因为它流行。下一步就是熟悉这个新环境了,如基本的系统设置、网络设置以及个性化定制等,这些不详述。

工作环境是一个常用软件的集合,在Windows下自不必说了,那些软件都是耳熟能详了。但是在Linux下又有哪些软件可以作为替代品呢?带着这样的目的,我开始了搭建Linux工作环境的历程。另外王垠(http://learn.tsinghua.edu.cn:8080/2001315450)曾在其主页上介绍过不少好用的工具软件,这里很多软件也都是源于王垠的介绍。

Linux下的软件安装一般有两种方法:
(1) 通过rpm方式
安装:rpm -i your-package.rpm
卸载:rpm -e your-package

(2) 通过源代码编译方式
源代码编译三部曲:configure –> make –> make install

我的Linux工作环境
(1) 强大的Bash
以前在Solaris上开发使用的都是C shell,而Linux默认的Shell却是Bash Shell。我初始感觉Bash Shell与C Shell不同之处包括可以自动匹配补齐命令行、支持UP和DOWN ARROW来选择前一个和后一个命令行。对于一个非系统工程师的开发人员来说有一份得心应手的Shell配置文件足矣。下面是我的一份配置文件,简单而灵活,关键一点是它完全能够满足我的需求:
/* .bashrc */
# Tony Bai's .bashrc

#
# Source global definitions
#
if [ -f /etc/bashrc ]; then
        . /etc/bashrc   # –> Read /etc/bashrc, if present.
fi

#
# Greetings
#
echo "*********************************"
echo "***     This is Tony Bai      ***"
echo "*** Welcome to my linux world ***"
echo "*********************************"

function _exit()        # function to run upon exit of shell
{
        echo "********************"
        echo "***   Bye Bye!   ***"
        echo "*** Welcome Back ***"
        echo "********************"
}
trap _exit EXIT

#
# Export environment variables
#
CVSROOT=:pserver:tony@127.0.0.1:/export/home/cvs/CVS-ROOT
PROJDIR=/home/administrator/proj/example
PATH=.:$PATH:$HOME/bin:.local/bin

export CVSROOT
export PROJDIR

#
# User specific aliases and functions
#

# System command set
alias rm='rm -i'
alias mv='mv -i'
alias mkdir='mkdir -p'
alias h='history'
alias which='type -all'
alias ..='cd ..'
alias path='echo -e ${PATH//:/\\n}'
alias du='du -kh'
alias df='df -kTh'
alias la='ls -Al'               # show hidden files
alias ls='ls -hF –color'       # add colors for filetype recognition
alias lx='ls -lXB'              # sort by extension
alias lk='ls -lSr'              # sort by size
alias lc='ls -lcr'              # sort by change time
alias lu='ls -lur'              # sort by access time
alias lr='ls -lR'               # recursive ls
alias lt='ls -ltr'              # sort by date
alias lm='ls -al |more'         # pipe through 'more'

# Compile
alias gcc='gcc -Wall'

# System info Viewer
alias cpu='cat /proc/cpuinfo'
alias mem='cat /proc/meminfo'
alias version='cat /proc/version'
alias ipconfig='/sbin/ifconfig'

# Project info
alias cdinc='cd $PROJDIR/include'
alias cdsrc='cd $PROJDIR/src'

另外修改.bashrc后别忘了执行'bash'使配置修改生效。

(2) 输入法
毕竟是开发中文程序,中文输入发必不可少。虽觉得Fedora自带的“智能拼音”不错,但是“小企鹅输入法(free Chinese Input Toy for X)”的定制功能却让我更加垂涎。遂在小企鹅输入法网站上下载了专门为Fedora Core 4制作的rpm。安装后我们就可以修改~/.fcitx/config文件来订制你个性化的输入法了,如果你在Windows上使用微软输入法习惯了,我们完全可以把“小企鹅输入法”变成Linux上的“微软输入法”。

(3) 浏览器
无论在任何平台上我们都不能忽略网络世界的存在,在Windows上有IE,在Linux上我们有Mozilla Firefox这一新宠儿。关于Firefox的资料太多太多,我想这里就毋庸讳言了。

(4) 邮件工具
Evolution, 一款在使用习惯上颇为接近于Microsoft Outlook的邮件客户端及个人信息管理程序,如果你是用惯了Outlook的用户,那么Evolution将是你在Linux上的一个不错的选择。Evolution是Linux自带的程序,无需你下载安装了。

(5) 编辑器
对于一名程序员来说获得一得心应手的编辑器就好比如虎添翼一般。Linux给你提供了多种选择,既有图形界面的,又有基于终端的。不过VI/VIM仍然是我的最爱。

(6) 开发工具
由于做后台服务端开发,所以必不可少的需要Gcc, make等工具, Linux上还默认提供automake, autoconf等工具,免去了你手工编写Makefile的烦恼,不过要掌握这些工具也需要一个过程,自己权衡吧^_^。

(7) 词典工具
王垠在其文章中提到了WordNet,对该软件的新颖的概念很是感兴趣,遂down了一个,不过遗憾的是没有编译通过,至今未找到原因。

(8) 娱乐工具
程序员在工作之余都喜欢看看电影,而MPlayer又是被公认在Linux下最好的媒体播放软件。遗憾的是我的机器上没有声卡,不能听到MPlayer输出的优美音乐。

(9) 办公工具
由于公司的文档都是由微软的工具产生的,要想在Linux下阅读和修改可不是件容易事。试过了Linux自带的OpenOffice,PPT文档还可以,Word文档简直就不堪入目了。王垠推荐将Word等先转换为html网页再查看,我很懒嫌麻烦。想起金山最新推出的WPS2005在Windows下的效果还不错,希望金山也能尽快推出Linux下的WPS版本。来解决这一使用Linux办公的最大难题。

(10) 通讯工具
对于使用QQ的人,LumaQQ相信是最好的选择;而Gaim是一个支持多种IM协议的工具,只是上手不是很容易罢了。

初接触Linux,试用了上面的一些工具,还处于经验积累阶段。

[注1]
我是参考http://fedora.Linuxsir.org上的安装说明一步一步做的,感觉还不错。

汇编之路-复习栈操作

不得不承认上次关于栈桢和栈操作写得有些笼统,这里做一次“补充”,美名其曰:“复习”。

下面的这个例子几乎就能覆盖所有的栈操作相关的内容了。
void dummy()
{
        int     i = 12;
        int     j = 13;
        char    c = 'a';
}

int main()
{
        dummy();
        return 0;
}

下面是利用MDB(注[1])反汇编的代码:
> main::dis
main:                           pushl   %ebp
main+1:                         movl    %esp,%ebp
main+3:                         subl    $8,%esp
main+6:                         andl    $0xf0,%esp
main+9:                         movl    $0,%eax
main+0xe:                       subl    %eax,%esp
main+0×10:                      call    -0x2a          
main+0×15:                      movl    $0,%eax
main+0x1a:                      leave
main+0x1b:                      ret

> dummy::dis
dummy:                          pushl   %ebp
dummy+1:                        movl    %esp,%ebp
dummy+3:                        subl    $0xc,%esp
dummy+6:                        movl    $0xc,-4(%ebp)
dummy+0xd:                      movl    $0xd,-8(%ebp)
dummy+0×14:                     movb    $0×61,-9(%ebp)
dummy+0×18:                     leave
dummy+0×19:                     ret

分析上面的汇编代码我们要解决如下几个方面问题:
1、过程调用的标准模式
我们知道发生过程调用的指令是call,那么call做了些什么呢?上面每个过程的最后都有leave指令,它又作了什么呢?我们不妨来跟踪一个栈帧的形成过程,分析后自然会有答案。

(1) 我们从main + 0×10处开始,这里是一个call指令,此时的活动栈帧为main的栈帧,dummy栈帧尚未形成:
+          + 0xffffffff
|          |
+———-+
|          | main的返回地址,属于main的调用者栈帧范畴
+———-+ —————————
|    A     | main栈帧栈底 <– %ebp
+———-+
|    B     |
+———-+
|    C     | main栈帧栈顶 <– %esp
+———-+
|          |
+          + 0×00000000

(2) 调用call指令后,未执行dummy前,此时main的栈帧已经结束,%eip中存放dummy起始指令地址准备执行。
+          + 0xffffffff
|          |
+———-+
|          | main的返回地址,属于main的调用者栈帧范畴
+———-+ —————————
|    A     | main栈帧栈底 <— %ebp
+———-+
|    B     |
+———-+
|    C     |
+———-+
|          | dummy的返回地址, main栈帧栈顶 <– %esp
+———-+ —————————
|          |
+          + 0×00000000
可见call首先将main调用的函数(这里是dummy)的返回地址pushl到栈中,形成main栈帧的最后一个部分,然后跳到dummy的起始处。所以call等价于下面两条指令:
pushl %eip  //将下一条指令地址压入栈中
jmp dummy

(3) 形成dummy栈帧
dummy首先将main的栈底保存起来,然后创建自己的栈底。
+          + 0xffffffff
|          |
+———-+
|          | dummy的返回地址,属于main的栈帧范畴
+———-+ —————————
|    D     | dummy栈帧栈底 <– %ebp,存储着main栈帧栈底
+———-+
|    E     |
+———-+
|    F     | dummy栈帧栈顶 <– %esp
+———-+ —————————
|          |
+          + 0×00000000

(4) dummy返回
dummy返回时调用的第一条指令leave,该指令相当于如下两条指令:
指令1: movl %ebp %esp // 将%esp置到dummy栈桢首部

该指令执行后状态如下:
+          + 0xffffffff
|          |
+———-+
|          | dummy的返回地址,属于main的栈帧范畴
+———-+ —————————
|    D     | dummy栈帧栈底 <– %esp <– %ebp
+———-+
|    E     |
+———-+
|    F     | dummy栈帧栈顶
+———-+ —————————
|          |
+          + 0×00000000

指令2:popl %ebp
该指令执行后状态如下:
+          + 0xffffffff
|          |
+———-+
|          | main的返回地址,属于main的调用者栈帧范畴
+———-+ —————————-
|    A     | main栈帧栈底 <— %ebp
+———-+
|    B     |
+———-+
|    C     |
+———-+
|          | dummy的返回地址,main栈帧栈顶 <– %esp
+———-+ —————————
|    D     | dummy栈帧栈底
+———-+
|    E     |
+———-+
|    F     | dummy栈帧栈顶
+———-+ —————————
|          |
+          + 0×00000000

dummy返回时调用的第二条指令ret,该指令相当于popl %eip,执行完内存栈的情况如下:
+          + 0xffffffff
|          |
+———-+
|          | main的返回地址,属于main的调用者栈帧范畴
+———-+ —————————-
|    A     | main栈帧栈底 <— %ebp
+———-+
|    B     |
+———-+
|    C     | <– %esp main栈帧栈顶
+———-+
|          | dummy的返回地址
+———-+ —————————
|    D     | dummy栈帧栈底
+———-+
|    E     |
+———-+
|    F     | dummy栈帧栈顶
+———-+ —————————
|          |
+          + 0×00000000

至此,main的栈桢又再次被恢复了。

经过上面分析,得出过程调用标准模式如下:
pushl %ebp
movl %esp %ebp

//过程体

leave
ret
其中ret和call对应,而leave则和最开始的那两句对应。

2、访问局部变量
在dummy的汇编码中我们可以清晰的看到对三个局部变量i,j,c的赋值语句:
movl    $0xc,-4(%ebp)
movl    $0xd,-8(%ebp)
movb    $0×61,-9(%ebp)
其三者有一个共同点就是“都是通过对%ebp的偏移来访问局部变量的”。

3、局部变量的分配
两个以上的局部变量的栈上分配涉及到栈内存的对齐问题,dummy的代码足以说明问题。我们在dummy的栈桢中分配了两个整型和一个char型变量,实际需要9个字节。那我们来看看汇编是否给我们只分配了9个字节呢?
movl    %esp,%ebp
subl    $0xc,%esp
movl    $0xc,-4(%ebp)

可以看出subl $0xc,%esp一句在内存栈上为我们留出12个字节的空间,在char c的后面又多分了3个字节,以保证对后面的变量的地址访问是对齐的。

4、对异构类型变量的分配和访问
举例如下:
struct test_t {
        int i;
        int j;
        int a[3];
};

void dummy()
{
        struct test_t t;
        t.i = 11;
        t.j = 12;
        t.a[0] = 'a';
        t.a[1] = 'b';
        t.a[2] = 'c';
}

int main()
{
        dummy();
        return 0;
}

> dummy::dis
dummy:                          pushl   %ebp
dummy+1:                        movl    %esp,%ebp
dummy+3:                        subl    $0×28,%esp
dummy+6:                        movl    $0xb,-0×28(%ebp)
dummy+0xd:                      movl    $0xc,-0×24(%ebp)
dummy+0×14:                     movl    $0×61,-0×20(%ebp)
dummy+0x1b:                     movl    $0×62,-0x1c(%ebp)
dummy+0×22:                     movl    $0×63,-0×18(%ebp)
dummy+0×29:                     leave
dummy+0x2a:                     ret

与上面的例子不同的是这次为了存储一个test_t类型结构,栈居然留出了0×28(40d)大小的空间,在t.a[2]与%ebp之间留了0×14(20)个字节空闲。这里的原因不得而知。如果是为了对齐,那么这个代价着实不小。

[注1]
在X86平台的Solaris9上,GDB反汇编使用的语法与我们的稍有差异,而使用Solaris自带的MDB(The Modular Debugger)则和我们的汇编语法保持一致。顺便说一句MDB是一个强大的调试工具,在Sun公司的网站上有其详细的使用说明。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats