linux宝库/编程技术/Unix编程/应用问答中文版
来源:百度文库 编辑:神马文学网 时间:2024/04/28 01:50:41
Unix编程/应用问答中文版
2005-08-24 10:00 am
作者:linux宝库 (http://www.linuxmine.com)
来自:linux宝库 (http://www.linuxmine.com)
现存:http://www.linuxmine.com/3692.html
联系:linuxmine#gmail.com
不明白?欢迎到linux论坛 (http://bbs.linuxmine.com) 参加讨论!
名称 -- Unix编程/应用问答中文版
版本 -- 0.03 ( 2002-03-06 外发版 )
维护 -- 小四
主页 -- http://www.nsfocus.com
创建 -- 2001-02-05 13:49
更新 -- 2002-03-03 17:42
感谢 --
感谢C语言的发明者、Unix操作系统的发明者、感谢全世界C程序员创造的Unix共
享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。
感谢我的朋友,deepin,在整个维护过程中的支持、帮
助和鼓励。感谢我所有的NSFocus安全研究小组的朋友(tt、yuange、security@
nsfocus.com ... ...),是他们容忍我在这个非正业上花费时间。
主要支持人员(字母顺序) --
Andrew Gierth
backend
Casper Dik
deepin
scz
suxm
tt
简介 --
这份文档不是FAQ(Frequently Answered Question),不少问题属于FUQ(Freque-
ntly Unanswered Question)。换句话说,不一定是最常见的编程、应用问答,很可
能其中的答案尚是一个构思,还没有成为现实,又或者根本是个错误的思想火花。但
是,她的确在试图回答一些很有意义的问题,让更多的Unix/C程序员、系统管理员共
享彼此的智慧,那是三十年前无数前辈精英做到过的,也是我们正试图做到的。
Q -- Question
A -- Answer
D -- Discuss
声明 --
永久拒绝任何商业性质的转载、摘录、引用。在不对所有文字做任何修正的前提
下,允许一切教育性质的转载、摘录、引用,无须提前知会维护者(就是me,faint)。
一旦出现需要修正文字的情况,只能通过维护者修正。维护者会在下一次版本升级过
程中正式增加这种修正,保留提供修正者应有信息。同时意味着提供修正者永久自愿
放弃商业性质的所有权益。不接受这种条件的提供修正者,务必提前知会维护者,此
类修正将不出现在下一次版本升级中。
文中所附各种源代码,在严格意义上可能存在版权问题,所以事实上这份文档带
有"半地下"性质,使用者务必自己小心卷入此类纠纷。
文中技术可能涉及未公开的、未文档化的、非规范的编程、应用接口,文档提供
的重在思想,而不保证是正确、高效、唯一的解答。
维护者不对文中任何技术引起的任何灾难性后果负任何法律上的、道义上的责任。
Ok, Let‘s go.
辅助说明 --
2002-03-06 12:14
辅助说明只在"外发版"中存在,稍微解释一下。
一直没有单独出一份完整的,原因很多。如果搁在1995/1996/1997时的CERNET,
这些原因都不成为原因,现在成为原因。不想多说为什么,明白的自然明白,不
明白的当我白痴好了,反正别问我。
出于"声明"中的某些理由,不能在单份完整文档中附带可能会带来麻烦的文字、
代码,比如Solaris libproc编程接口。但是,在散篇中你能找到它们。如果你
愿意,可以自己将散篇收回到该文档中,这将与我无关。一切索要残缺部分的邮
件盖不回复。
本份文档的绝大多数内容在"中国教育科研网华南地区网络中心BBS"(bbs.gznet.
edu.cn)的Solaris版发布过了,包括下面处理掉的目录列表。是该版前版主CPU
师兄当年的风范促使我开始整理这份文档的,当还昔日指教之情谊。
该份文档"允许一切教育性质的自由转载、摘录、引用,无须提前知会维护者"。
我也只是义务维护一下,不对本文档拥有任何权益。如果不幸潜在拥有而践踏了
某种信念,在你看到该辅助说明的同时,我将自动放弃这种潜在可能拥有的权益。
同时意味着一切因本文档带来的麻烦,将由你个人承担。
既然来自Unix共享传统文化圈,就让它彻底回到Unix共享传统文化圈中去吧。
欢迎一切建设性的、非索要性质的Email交流。
--------------------------------------------------------------------------
目录
0. Unix/C传奇问题
0.1 Dennis Ritchie 和 Ken Thompson
0.2 W. Richard Stevens 之死
1. 系统管理配置问题
1.1 如何给SUN工作站增加eeprom硬件口令保护
1.2 如何增加交换空间
1.3 为什么我不能在/home目录下创建子目录
1.4 如何改变一台主机的locale
1.5 Solaris 7自动注销
1.6 一个目录拥有setgid设置,怎么理解
1.7 非Sun Console上有无等价Stop-A的按键
1.8 如何让一个用户只能ftp而无法telnet
1.9
1.10 为什么Sun工作站非要输入boot命令才能启动
1.11 如何让Solaris识别新增加的硬件
1.12
2. 堆栈相关问题
2.1 如何理解pstack的输出信息
2.2
2.3 Solaris中如何获取一个C程序的调用栈回溯
2.4 如何编程获取栈底地址
2.5 如何得到一个运行中进程的内存映像
2.6 调试器如何工作的
2.7 x86/Linux上如何处理SIGFPE信号
3. -lelf、-lkvm、-lkstat相关问题
3.1 如何判断可执行文件是否携带了调试信息
3.2 mprotect如何用
3.3 mmap如何用
3.4 getrusage如何用
3.5 setitimer如何用
4. 系统资源相关问题
4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况
4.2 Solaris下如何获知CPU速率
4.3 如何编程获取Solaris系统当前内存大小
5. 块设备相关问题
5.1 CDROM设备究竟在哪里
5.2 如何弹出光驱
5.3 如何利用超级块进行恢复工作
5.4 Solaris Root口令忘记了
5.5 如何使用fmthard
5.6 如何从光盘恢复Solaris 7的引导扇区
5.7 Solaris支持类似微软autorun.inf文件的功能吗
5.8 如何修改/dev/null的属性
5.9
5.10 如何自己制作Solaris启动软盘
5.11 x86/Solaris如何访问FAT32分区
6. /etc/system可调资源限制
6.1 Solaris下如何限制每个用户可拥有的最大进程数
6.2 如何配置系统使之支持更多的伪终端
6.3 如何增加每个进程可打开文件句柄数
6.4
6.5 做了setuid()这类调用的程序如何产生core dump
6.6 消息队列调整
7. DNS相关问题
7.1 如何进行DNS区传输
7.2 如何获知权威名字服务器
7.3 如何配置DNS的委托解析
7.4 如何获知BIND的版本号
7.5 Solaris/FreeBSD/Linux如何指定域名解析的顺序
8. Solaris内核编程相关问题
8.1 Solaris内核模块中如何getcwd
8.2
8.3 如何避免一个套接字进入TIME_WAIT状态
8.4 结构在优化编译中的对齐问题
8.5
8.6 如何得到非局部变量列表
8.7
8.8 如何单独获得Solaris编译环境
8.9 如何获取Solaris内核可调参数列表
8.10
8.11 如何页边界对齐式分配内存
8.12
8.13 compile()和step()怎么用
8.14
8.15
8.16
8.17
9. 图形界面相关问题
9.1 如何避免进入Solaris的图形界面
9.2 Solaris 7的锁屏
9.3 如何调整键盘重复率
9.4 如何拔掉键盘继续运行Solaris
9.5 Solaris下如何设置显卡分辨率
9.6 Solaris下如何设置显示刷新率
10. 网卡相关问题
10.1 如何在程序中获取本机MAC地址
10.2 如何在Sun工作站上安装3块网卡
10.3 如何在Solaris x86上安装网卡驱动
10.4 Solaris 单网卡多IP(以太网卡别名)
10.5 如何修改主机名(hostname)
10.6 SPARC/Solaris 2.5/2.6/7/8下如何设置网卡100Mb全双工
10.7 Unix/Linux/BSD如何对抗ARP欺骗攻击
10.8
10.9
10.10
10.11 x86/Solaris如何强制设定网卡速率
10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed
10.13
10.14 traceroute是怎么实现的
11. package相关问题
11.1 Solaris下如何将二进制软件包安装到指定目标路径下
11.2 Solaris下如何自己定制二进制安装包
11.3 如何恢复/usr/bin/su的缺省安装属性
11.4 如何获知指定包与其他包之间的依赖关系
11.5 Linux中如何知道ifconfig属于哪个包
11.6 Solaris下如何知道某包中有哪些文件
12. 日志相关问题
12.1
12.2
12.3 如何关闭cron的日志
12.4
13. 进程相关问题
13.1 如何根据进程名获得PID
13.2
13.3
13.4 Solaris 7/8下ps输出中的问号
13.5
13.6
13.7 给定一个PID,如何知道它对应一个运行中的进程
13.8 Unix/Linux编程中所谓"僵尸进程"指什么
13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页
13.10 Solaris下如何知道哪个进程使用了哪个端口
13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数
14. 一些小工具的使用
14.1
14.2
14.3 只在本地文件系统上查找
14.4
15. 32-bit/64-bit相关问题
15.1 Solaris下如何识别当前内核版本
15.2 如何启动Solaris 32-bit/64-bit内核
15.3 gcc支持64-bit编译吗
15.4 Solaris启动时内核文件找不到了
15.5 64-bit驱动程序无法在8下关联,但在7下工作正常
16. 库相关问题
16.1 在Solaris 7下编写网络程序需要链接哪些库
16.2
16.3 链接过程中库的顺序
16.4
16.5
16.6 /usr/lib/ld.so.1损坏或丢失
16.7
16.8
16.9 Solaris 8下如何配置运行时链接环境
17. 文件查看问题
17.1 如何直接查看man文件
17.2 .tex文件怎么读
17.3 Solaris下怎么看.ps文件
18. 补丁相关问题
18.1 如何根据补丁号从Sun主站下载补丁
18.2
18.3
18.4 给Solaris 2.6安装推荐补丁集
18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁
18.6 如何安装补丁
19. 终端相关问题
19.1 如何使Backspace键做删除操作,而不是显示^H
19.2
19.3 如何清空stdin的缓冲
19.4 Linux Console下一按错键就叫,怎么关
20. shell script问题
20.1 如何获取一个字符串的长度
20.2 读超时自动使用缺省值
20.3
20.4 BASH中如何得到一个字符串的子串
20.5
20.6
20.7
20.8 使用tr命令加密文件
20.9 有哪些命令用于查找定位
20.10
20.11 如何将大写文件名转换为小写文件名
21. FreeBSD相关问题
21.1
21.2 如何将一个512字节的文件写入主引导扇区
21.3
21.4
21.5
21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel
21.7 x86/FreeBSD下如何设置路由
21.8
21.9 什么是locale
21.10 用cvsup安装vim
21.11 FreeBSD下vi输入中文会显示\x??\x??
21.12
21.13
21.14
21.15 UDMA ICRC error是什么意思
21.16 Limiting closed port RST response什么意思
21.17
21.18
21.19
21.20
22. Linux Kernel Programming
22.1 直接访问内存[显存]地址
22.2
23. Linux相关问题
23.1
--------------------------------------------------------------------------
0. Unix/C传奇问题
0.1 Dennis Ritchie 和 Ken Thompson
Q: 我想知道他们,为什么大家不断提到这两个名字?
A: All of Unix Programmers
我们也想知道,:-P
1969年Dennis Ritchie 和 Ken Thompson在贝尔实验室创造性地发明了Unix操作系统,
为此1983年他们获得了图灵奖。
尽管Ritchie是C程序设计语言的发明者,但是他最喜欢的编程语言是Alef。而
Thompson是一名业余飞行员,曾到莫斯科驾驶过米格-29。
欢迎访问
http://cm.bell-labs.com/who/dmr/
http://cm.bell-labs.com/who/ken/
0.2 W. Richard Stevens 之死
Q: David Johns
我是他的崇拜者,用www.google.com搜索他的讣告,但这份讣告没有提及死因,有人
知道吗?
真地仅仅是英年早逝吗?
A: Nithyanandham
他死于1999/09/01,家人不想让别人知道死因。讣告位于
http://www.azstarnet.com/clips/richard_stevens.html
A: joe broz
似乎是一场攀岩事故,或者滑雪事故,我不确认。
1. 系统管理配置问题
1.1 如何给SUN工作站增加eeprom硬件口令保护
A: scz
man -s 1M eeprom了解细节,要求当前是root身份
# /usr/sbin/eeprom (显示当前eeprom配置)
# /usr/sbin/eeprom security-mode=full ( 可选的有command, full, none)
此时进入交互式设置口令过程,总共输入两次,如果两次口令输入不一致,则本次设
置作废。成功设置之后除了go命令之外的其他ok状态下命令均需要口令,包括boot命
令。
设置成command时,同样进入交互式口令输入过程。此时,除了boot和go命令之外的
其他ok状态下命令均需要口令。注意,如果仅仅输入boot命令,不需要口令,一旦
boot命令后面带了参数,比如boot cdrom -s,同样需要输入口令。
如果设置成none(缺省设置),表示去掉这种口令保护。
# /usr/sbin/eeprom security-password= (等号后面无其他字符,直接回车)
如果想改变前面设置的口令,用这条命令,同样是交互式输入过程。
# /usr/sbin/eeprom security-#badlogins=3 (缺省是0)
设置口令输入尝试次数。
警告:如果设置了eeprom硬件保护口令而又忘记,会带来很多麻烦,务必小心。
一个可行的设置办法是,安全模式设置到command而不是full,这样至少可以正常启
动系统。于是只要记得root口令或者还有其他机会获得root权限(缓冲区溢出?),就
可以通过设置安全模式为none而挽救回来。
但是如果设置成full模式却忘记了eeprom口令,我想首先应该打电话给SUN的技术支
持。如果出于某种理由你不想这样做,我不确认eeprom是否可以热插拔,先用一个无
口令保护的eeprom启动系统,然后热插拔换上那个有口令保护的eeprom,然后用root
权限抹去eeprom口令。
1.2 如何增加交换空间
A: WT
你无法改变分区大小,但是可以增加/删除交换文件,效果类似交换分区。下列命令
在根目录下创建一个500MB的交换文件,名为swapfile
# mkfile 500m /swapfile
下列命令将使之生效
# swap -a /swapfile
现在你有了额外的500MB交换空间,为了每次重启后依旧有效,编辑/etc/vfstab文件
增加如下行
/swapfile - - swap - no -
# swap -l
这里"-l"意味着"list",显示所有交换空间。仔细阅读"swap"和"mkfile"的手册页。
1.3 为什么我不能在/home目录下创建子目录
Q: Solaris 7下,root身份,当我试图在/home目录下创建子目录时,系统拒绝,为
什么?
A: mohansundarraj
如果/etc/rc2.d/S74autofs脚本中automount(1M)守护进程已经mount了/home,就是
这种现象,而这还是缺省安装后的情形。可以
# /etc/init.d/autofs stop
# umount /home
然后你就可以用root身份在/home下创建子目录,增加文件了。为了永久取消autofs
特性,可以将/etc/rc2.d/S74autofs脚本改名,并注释掉/etc/auto_home、
/etc/auto_master两个文件中的入口点。
SPARC/Solaris的缺省用户主目录是/export/home,而不是/home。
1.4 如何改变一台主机的locale
Q: 一台SPARC/Solaris 8运行在US locale环境中,现在我们想让它运行在
IE(Ireland) locale环境中,以便可以使用欧洲日期格式,怎么办?
A: Sharad Ramachandran
运行sys-unconfig,在此之前请man -s 1M sys-unconfig,:-)
A: chad schrock
天啊,为了拍死一只苍蝇,你要引爆原子弹吗?
只需要做如下操作,在你的.cshrc/.profile/.bashrc等启动脚本中设置$LANG环境变
量的值为en_UK,注销,重新登录即可。为了使这个设置全局有效,修改
/etc/default/init文件,LANG=en_UK,重启动。
--------------------------------------------------------------------------
# @(#)init.dfl 1.2 92/11/26
#
# This file is /etc/default/init. /etc/TIMEZONE is a symlink to this file.
# This file looks like a shell script, but it is not. To maintain
# compatibility with old versions of /etc/TIMEZONE, some shell constructs
# (i.e., export commands) are allowed in this file, but are ignored.
#
# Lines of this file should be of the form VAR=value, where VAR is one of
# TZ, LANG, or any of the LC_* environment variables.
#
TZ=GMT+8
LANG=zh.GBK
--------------------------------------------------------------------------
参看locale(1)和locale(5),了解更多关于locale的信息。运行"locale -a",查看
当前系统所支持的所有locale。
A: Sun Microsystems 2001-06-12
有三种方式改变locale。首先用"locale -a"命令确认系统中已安装的locale
1) 从CDE登录屏幕上修改locale
选择 options -> languages -> choose the new locale
注意,如果登录用户的初始化文件中有不同的locale设置,将优先于系统全局locale
设置。
2) 临时设置locale(shell相关的)
ksh : LANG=
sh : LANG=
export LANG
csh : setenv LANG
bash: export LANG=en_US(zh.GBK)
3) vi /etc/default/init
增加如下内容
LANG=
LC_ALL=
重启系统。
运行"locale"命令确认改变生效。
如果你希望使用的locale并未安装,参看如下文档安装locale
Solaris 8 : <>
Solaris 7 : <>
Solaris 2.6: <>
D: scz 1998-08
SPARC/Solaris 2.5下,为了在vi中正确看到中文需要设置环境变量
sh
LANG=C;export LANG
LC_CTYPE=iso_8859_1;export LC_CTYPE
csh
setenv LANG zh
关于设置LANG这个环境变量涉及到/usr/lib/locale下的目录权限。
1.5 Solaris 7自动注销
Q: 怎样设置才能30秒后自动注销
A: shridhara
不幸的是,Solaris对此没有什么好的支持。如果正在使用telnet会话,或许可以考
虑"logout"变量,参看telnet的手册页。一个变通的办法,使用K-Shell,它支持
TMOUT变量,用于指定非活动时限(以秒为单位)。比如,如果一个shell会话3分钟内
不活动,则终止这个shell会话
$ TMOUT=180;export TMOUT
可以在用户的.profile文件中放置该行。缺点是你只能使用ksh。
D: scz
vi /etc/default/login
# TIMEOUT sets the number of seconds (between 0 and 900) to wait before
# abandoning a login session.
#
TIMEOUT=180
这里的超时设置针对登录过程,而不是登录成功后的shell会话超时设置。
1.6 一个目录拥有setgid设置,怎么理解
Q: 对一个目录做了setgid设置,可我并没有发现这和正常情况有什么区别
A: John Riddoch
在这种目录下创建新文件时将采用setgid设置对应的属组,比如
$ ls -ld b
drwxrws--- 2 jr group 512 Mar 14 17:13 b/
$ touch b/a
$ ls -l b/a
-rw------- 1 jr group 0 Mar 14 17:13 b/a
$ id
uid=178(jr) gid=10(staff)
jr的缺省组是staff,而现在b/a文件属组是group。
D: 小四
SPARC/Solaris 7下测试
如果目录拥有SGID设置,那么该目录下新创建的文件将继承该目录的属组,而不是创
建者所对应的GID。
[root@ /export/home/scz]> id
uid=0(root) gid=1(other) <-- 注意当前用户的属组
[root@ /export/home/scz]> mkdir groupsgid
[root@ /export/home/scz]> ls -ld groupsgid
drwxr-xr-x root other groupsgid/
[root@ /export/home/scz]> chown scz:users groupsgid
[root@ /export/home/scz]> chmod g+s groupsgid
[root@ /export/home/scz]> ls -ld groupsgid
drwxr-sr-x scz users groupsgid/ <-- 目录拥有SGID设置
[root@ /export/home/scz]> cd groupsgid/
[root@ /export/home/scz/groupsgid]> touch scz_0
[root@ /export/home/scz/groupsgid]> ls -l scz_0
-rw-r--r-- root users scz_0 <-- 注意属组变化
[root@ /export/home/scz/groupsgid]> chmod g-s ../groupsgid/
[root@ /export/home/scz/groupsgid]> ls -ld ../groupsgid/
drwxr-xr-x scz users ../groupsgid/
[root@ /export/home/scz/groupsgid]> touch scz_1
[root@ /export/home/scz/groupsgid]> ls -l scz_1
-rw-r--r-- root other scz_1 <-- 注意属组变化
[root@ /export/home/scz/groupsgid]>
1.7 非Sun Console上有无等价Stop-A的按键
A: neomilev
如果是便携机,尝试alt/break 或者 ctrl/break。如果是vt100终端,尝试F11 或者
break
1.8 如何让一个用户只能ftp而无法telnet
A: 小四
修改该用户在/etc/passwd中的shell为/bin/false,在/etc/shells文件中增加
/bin/false,此时,该用户只能ftp,telnet失败。
1.10 为什么Sun工作站非要输入boot命令才能启动
Q: 我有台Sun工作站,每次开机后停在ok状态下,需要手工输入boot命令才能启动,
现在想避免这种效果,怎么办
A: /usr/sbin/eeprom auto-boot?=true
/usr/sbin/eeprom auto-boot? <-- 查询
A: dengdai@SMTH
进入OBP状态
ok setenv auto-boot? true
ok setenv boot-device disk
反之
ok setenv auto-boot? false
1.11 如何让Solaris识别新增加的硬件
Q: 比如新增加了网卡、硬盘、光驱什么的,如何让Solaris意识到这种增加
A: spp(低音炮) & suxm
有三种办法
a. Stop-A进入OBP状态,输入boot -r
b. sync(重复);reboot -- -r
c. touch /reconfigure;sync(重复);reboot
参看reboot(1M)、boot(1M)、eeprom(1M)、kernel(1M)、cfgadm(1M)、psradm(1M)手
册页
Q: 我新增加了一块硬盘,不想boot -r而立即生效,怎么办
A: 老大 2001-12-04 16:51
直接将第二块硬盘接上去,然后顺序执行如下命令,不用重新启动机器
modunload -i 0
drvconfig(1M)
devlinks(1M)
disks(1M)
如果需要重新格式化、分区、创建文件系统,就继续执行
format(1M)
newfs(1M)
2. 堆栈相关问题
2.1 如何理解pstack的输出信息
Q: 080603a7 main (1, 80479b8, 80479c0) + d53
结尾的d53是什么
A: Roger A. Faulkner
在代码段绝对地址0x080603a7处,main()调用了一个函数,0x080603a7正是
main + 0xd53,换句话说,从main()函数开始的0xd53偏移处。
2.3 Solaris中如何获取一个C程序的调用栈回溯
Q: 我想在Solaris 2.6极其后续版本上获取一个C程序的调用栈回溯,类似如下输出
(10) 0x00045e08 integ + 0x408 [./two_brn.e]
(11) 0x0006468c trajcem + 0x128 [./two_brn.e]
(12) 0x00055490 fly_traj + 0xf58 [./two_brn.e]
(13) 0x0004052c top_level + 0x14 [./two_brn.e]
(14) 0x000567e4 _start + 0x34 [./two_brn.e]
这样我就可以知道当程序崩溃、死锁的时候代码执行到了何处。在HP-UX和IRIX上
可以利用U_STACK_TRACE()和trace_back_stack_and_print(),Solaris上呢?
Q: 有没有办法显示当前堆栈中的数据(GNU/Linux系统)?我希望自己的异常处理程序
在进程结束前dump整个栈区(stack),以便观察到栈顶是什么函数。对于调试意想
不到的运行时错误而言,这很重要。
A: Bjorn Reese
用/usr/proc/bin/pstack [-F]
参看这个例子代码,http://home1.stofanet.dk/breese/debug/debug.tar.gz
Q: is there a way to access call stack information at run time from within
a program? i‘ve been maintaining my own crude stack using __FUNCTION__
and linked lists but can‘t help but think there‘s gotta be a better
way...
A: Nate Eldredge
这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数,
参看,其他系统可能有不同的技术支持。
注意,你所使用的办法可能是唯一能够保证跨平台使用的
A: Andrew Gabriel Consultant Software Engineer
下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那
么这个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,
好像Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时
间了。
/*
* Produce a stack trace for Solaris systems.
*
* Copyright (C) 1995-1998 Andrew Gabriel
* Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
*
*/
/* ......................................................................... */
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(sparc) || defined(__sparc)
#define FLUSHWIN() asm("ta 3");
#define FRAME_PTR_INDEX 1
#define SKIP_FRAMES 0
#endif
#if defined(i386) || defined(__i386)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 3
#define SKIP_FRAMES 1
#endif
#if defined(ppc) || defined(__ppc)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 0
#define SKIP_FRAMES 2
#endif
/* ......................................................................... */
static void print_address ( void * pc )
{
Dl_info info;
if ( dladdr( pc, &info ) == 0 )
{
/* not found */
fprintf( stderr, "*** %s:0x%x\n", "??", ( unsigned int )pc );
}
else
{
/* found */
fprintf( stderr, "*** %s:%s+0x%x\n", info.dli_fname, info.dli_sname,
( unsigned int )pc - ( unsigned int )info.dli_saddr );
}
return;
} /* end of print_address */
/* ......................................................................... */
static int validaddr ( void * addr )
{
static long pagemask = -1;
char c;
if ( pagemask == -1 )
{
pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 );
}
addr = ( void * )( ( long )addr & pagemask );
if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM )
{
return 0; /* invalid */
}
else
{
return 1; /* valid */
}
} /* end of validaddr */
/* ......................................................................... */
/*
* this function walks up call stack, calling print_addess
* once for each stack frame, passing the pc as the argument.
*/
static void print_stack ( void )
{
struct frame * sp;
jmp_buf env;
int i;
int * iptr;
FLUSHWIN();
setjmp( env );
iptr = ( int * )env;
sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ];
for ( i = 0; i < SKIP_FRAMES && sp; i++ )
{
if ( !validaddr( sp ) || !validaddr( &sp->fr_savpc ) )
{
fprintf( stderr, "***[stack pointer corrupt]\n" );
return;
}
sp = ( struct frame * )sp->fr_savfp;
}
i = 100; /* looping check */
while ( validaddr( sp ) && validaddr( &sp->fr_savpc ) && sp->fr_savpc && --i )
{
print_address( ( void * )sp->fr_savpc );
sp = ( struct frame * )sp->fr_savfp;
}
} /* end of print_stack */
/* ......................................................................... */
void backtrace( void )
{
fprintf( stderr, "***backtrace...\n" );
print_stack();
fprintf( stderr, "***backtrace ends\n" );
}
/* ......................................................................... */
2.4 如何编程获取栈底地址
Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序
获取这个栈底地址。
A: tt 2001-06-02 19:40
假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址
x86/Linux 栈底是0xc0000000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 2.6 栈底是0xf0000000( 栈底往低地址的4个字节总是零 )
x86/FreeBSD 栈底是0xbfc00000( 栈底往低地址的4个字节总是零 )
x86/NetBSD 1.5 栈底是0xbfbfe000
x86/OpenBSD 2.8 栈底是0xdfbfe000
D: jonah
对于NetBSD 1.5,栈底是0xbfc00000。根据源码,最高用户地址是0xbfbfe000,因为
最后4MB(2^22)的最后两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再
使用这块内存。因此,0xbfbfe000才是真正的栈底。
tt在OpenBSD 2.8上测试结果,栈底是0xdfbfe000,注意和NetBSD 1.5相差很大。
A: tt
--------------------------------------------------------------------------
/*
* gcc -Wall -O3 -o gstack gstack.c
*
* A simple example to get the current stack bottom address
* warning3
* 2001-06-01
*
* Modified by scz
* 2001-06-02
*/
#include
#include
#include
#include
#include
typedef void Sigfunc ( int ); /* for signal handlers */
Sigfunc * signal ( int signo, Sigfunc * func );
static Sigfunc * Signal ( int signo, Sigfunc * func );
static char * get_stack_bottom ( void );
static void segfault ( int signo );
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump = 0;
static Sigfunc *seg_handler;
static Sigfunc *bus_handler; /* for xxxBSD */
Sigfunc * signal ( int signo, Sigfunc * func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() function */
{
Sigfunc * sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static char * get_stack_bottom ( void )
{
volatile char *c; /* for autovar, must be volatile */
seg_handler = Signal( SIGSEGV, segfault );
bus_handler = Signal( SIGBUS, segfault );
c = ( char * )&c;
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
{
Signal( SIGSEGV, seg_handler );
Signal( SIGBUS, bus_handler );
return( ( char * )c );
}
canjump = 1; /* now sigsetjump() is OK */
while ( 1 )
{
*c = *c;
c++;
}
return( NULL );
} /* end of get_stack_bottom */
static void segfault ( int signo )
{
if ( canjump == 0 )
{
return; /* unexpected signal, ignore */
}
canjump = 0;
siglongjmp( jmpbuf, signo ); /* jump back to main, don‘t return */
} /* end of segfault */
int main ( int argc, char * argv[] )
{
fprintf( stderr, "Current stack bottom is at 0x%p\n", get_stack_bottom() );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
D: scz 2001-06-03 00:38
W. Richard Stevens在<>中详细
介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。
这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV
信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。
tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD,
NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV
信号。
非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号
句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此
时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句
柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了
保存恢复,有些系统没有做。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函
数。下面来自SPARC/Solaris 7的setjmp(3C)
--------------------------------------------------------------------------
#include
int setjmp ( jmp_buf env );
int sigsetjmp ( sigjmp_buf env, int savemask );
void longjmp ( jmp_buf env, int val );
void siglongjmp ( sigjmp_buf env, int val );
--------------------------------------------------------------------------
如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回
来的时候从env中恢复信号屏蔽字。
数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有
虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是
与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。
在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也
保持不变。
无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom()
中声明c为volatile变量。
注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、
SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指
令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一
次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用
长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果
在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。
D: scz 2001-06-03 00:40
在x86/Linux系统中用如下命令可以确定栈区所在
# cat /proc/1/maps <-- 观察1号进程init
... ...
bfffe000-c0000000 rwxp fffff000 00:00 0
#
在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在
# /usr/proc/bin/pmap 1 <-- 观察1号进程init
... ...
FFBEC000 16K read/write/exec [ stack ]
#
16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000
与前面tt介绍的
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
相符合。
此外,在SPARC/Solaris 7下,可以这样验证之
# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|_userlimit"
[7015] |0x0000100546f8|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit
[8051] |0x000010054700|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit32
# echo "_userlimit /J" | adb -k /dev/ksyms /dev/mem
physmem 3b72
_userlimit:
_userlimit: ffffffff80000000
# skd64 0x000010054700 8
byteArray [ 8 bytes ] ---->
0000000000000000 00 00 00 00 FF BF 00 00
# ~~~~~~~~~~~ 对于32-bit应用程序来说,这是用户
空间上限
如果编译64-bit应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000
# /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o gstack gstack.c
# ./gstack
Current stack bottom is at 0xffffffff80000000
#
对于SPARC/Solaris 2.6 32-bit kernel mode
# echo "_userlimit /X" | adb -k /dev/ksyms /dev/mem
physmem 3d24
_userlimit:
_userlimit: f0000000
#
2.5 如何得到一个运行中进程的内存映像
A: Sun Microsystems 1998-03-30
有些时候必须得到一个运行中进程的内存映像而不能停止该进程,Solaris系统了这
样的工具,gcore为运行中进程创建一个core文件。假设我的bash进程号是5347
# gcore 5347
gcore: core.5347 dumped
# file core.5347
core.5347: ELF 32-位 MSB core文件 SPARC 版本 1,来自‘bash‘
#
注意,只能获取属主是你自己的进程的内存映像,除非你是root。
2.6 调试器如何工作的
Q: 我想在一个自己编写的程序中单步运行另外一个程序,换句话说,那是一个调试
器,该如何做?
A: Erik de Castro Lopo
这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我
不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN
和BSD 4.3都支持它。
为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用:
ptrace( PTRACE_TRACEME, 0, 0, 0 );
接下来调用exec()家族的函数执行你最终企图跟踪的程序。
为了单步进入子进程,在父进程中调用:
ptrace( PTRACE_SINGLESTEP, 0, 0, 0 );
还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。
GDB的源代码足以回答这个问题。
2.7 x86/Linux上如何处理SIGFPE信号
Q: 参看如下程序
--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c
*
* 注意与下面的编译效果进行对比,去掉优化开关-O3
*
* gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c
*/
#include
#include
#include
#include
#include
#include
/*
* for signal handlers
*/
typedef void Sigfunc ( int );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void on_fpe ( int signo );
Sigfunc * signal ( int signo, Sigfunc *func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func )
{
Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void on_fpe ( int signo )
{
fprintf( stderr, "here is on_fpe\n" );
return;
} /* end of on_fpe */
int main ( int argc, char * argv[] )
{
unsigned int i;
Signal( SIGFPE, on_fpe );
i = 51211314 / 0;
/*
* 另外,增加这行后,再次对比有-O3和无-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
有-O3、无-O3,以及有无最后那条fprintf()语句,效果上有差别,自行对比。如果
输出"here is on_fpe",则会发现永不停止。
D: 小四 2001-12-14 18:25
为了便于讨论,约定两个名词,中断和异常。这里中断指最常规的中断,比如int指
令带来的软中断。异常的典型代表有除0错。区别在于,发生异常时,x86架构上CPU
将当前EIP(指向引发异常的指令)压栈,发生中断时,x86架构上CPU将当前EIP的后一
个地址(指向引发中断的指令的后一条指令)压栈。在异常处理代码中,如果认为能够
从灾难中恢复,可以不修改被压栈的EIP,从而返回到引发异常的指令处。更多细节
请查看Intel手册。
这些是从前DOS下残留的汇编知识,不过也快忘光了,刚才又找元宝宝确认了一下。
在上述代码中,on_fpe()直接返回了,导致再次触发异常,所以无休止输出。事实上
在所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的
时候又碰上过。正确的做法是,利用远跳转转移,让开引发异常的指令。
代码修改如下
--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c
*
* 注意与下面的编译效果进行对比,去掉优化开关-O3
*
* gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.c
*/
#include
#include
#include
#include
#include
#include
/*
* for signal handlers
*/
typedef void Sigfunc ( int );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void on_fpe ( int signo );
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump = 0;
Sigfunc * signal ( int signo, Sigfunc *func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func )
{
Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void on_fpe ( int signo )
{
if ( canjump == 0 )
{
return; /* unexpected signal, ignore */
}
canjump = 0;
fprintf( stderr, "here is on_fpe\n" );
siglongjmp( jmpbuf, signo ); /* jump back to main, don‘t return */
return;
} /* end of on_fpe */
int main ( int argc, char * argv[] )
{
unsigned int i;
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
{
fprintf( stderr, "c u later\n" );
return( EXIT_SUCCESS );
}
/*
* now sigsetjump() is OK
*/
canjump = 1;
Signal( SIGFPE, on_fpe );
i = 51211314 / 0;
/*
* 另外,增加这行后,再次对比有-O3和无-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
关于-O3的讨论,对gcc编译器熟悉的朋友请继续,呵,我对Linux下的这此东西,实
在缺乏兴趣。
3. -lelf、-lkvm、-lkstat相关问题
3.1 如何判断可执行文件是否携带了调试信息
Q: 某些时候需要知道编译可执行文件时是否携带了调试信息(比如是否指定了-g编译
选项)。检查可执行文件中是否包含".stab" elf section,".stab" section用于
保存相关调试信息。
A: Sun Microsystems 2000-05-15
下面这个脚本演示如何判断可执行文件是否携带调试信息
--------------------------------------------------------------------------
#! /bin/sh
#
# Script that test whether or not a given file has been built for
# debug (-g option specified in the compilation)
if [ $# -le 0 ]
then
echo "Usage: $1 filename"
exit 1
fi
if [ ! -f $1 ]
then
echo "File $1 does not exist"
exit 1
fi
/usr/ccs/bin/dump -hv $1 | /bin/egrep -s ‘.stab$‘
if [ $? -eq 0 ]
then
echo "File ‘$1‘ has been built for debug"
exit 0
else
echo "File ‘$1‘ has not been built for debug"
exit 1
fi
--------------------------------------------------------------------------
如果对ELF文件格式不熟悉,理解上述代码可能有点困难,参看
http://www.digibel.org/~tompy/hacking/elf.txt,这是1.1版的ELF文件格式规范。
3.2 mprotect如何用
A: 小四
# truss prtconf 2>&1 | grep sysconf
sysconfig(_CONFIG_PAGESIZE) = 8192
sysconfig(_CONFIG_PHYS_PAGES) = 16384
#
由此可知当前系统页尺寸是8192字节。
--------------------------------------------------------------------------
/*
* gcc -Wall -g -ggdb -static -o mtest mtest.c
*/
#include
#include
#include
#include
int main ( int argc, char * argv[] )
{
char *buf;
char c;
/*
* 分配一块内存,拥有缺省的rw-保护
*/
buf = ( char * )malloc( 1024 + 8191 );
if ( !buf )
{
perror( "malloc" );
exit( errno );
}
/*
* Align to a multiple of PAGESIZE, assumed to be a power of two
*/
buf = ( char * )( ( ( unsigned int )buf + 8191 ) & ~8191 );
c = buf[77];
buf[77] = c;
printf( "ok\n" );
/*
* Mark the buffer read-only.
*
* 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数
*/
if ( mprotect( buf, 1024, PROT_READ ) )
{
perror( "\nmprotect" );
exit( errno );
}
c = buf[77];
/*
* Write error, program dies on SIGSEGV
*/
buf[77] = c;
exit( 0 );
} /* end of main */
--------------------------------------------------------------------------
$ ./mtest
ok
段错误 (core dumped) <-- 内存保护起作用了
$
3.3 mmap如何用
A: 小四
下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。
--------------------------------------------------------------------------
/*
* gcc -Wall -O3 -o copy_mmap copy_mmap.c
*/
#include
#include
#include /* for memcpy */
#include
#include
#include
#include
#include
#include
#define PERMS 0600
int main ( int argc, char * argv[] )
{
int src, dst;
void *sm, *dm;
struct stat statbuf;
if ( argc != 3 )
{
fprintf( stderr, " Usage: %s
2005-08-24 10:00 am
作者:linux宝库 (http://www.linuxmine.com)
来自:linux宝库 (http://www.linuxmine.com)
现存:http://www.linuxmine.com/3692.html
联系:linuxmine#gmail.com
不明白?欢迎到linux论坛 (http://bbs.linuxmine.com) 参加讨论!
名称 -- Unix编程/应用问答中文版
版本 -- 0.03 ( 2002-03-06 外发版 )
维护 -- 小四
主页 -- http://www.nsfocus.com
创建 -- 2001-02-05 13:49
更新 -- 2002-03-03 17:42
感谢 --
感谢C语言的发明者、Unix操作系统的发明者、感谢全世界C程序员创造的Unix共
享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。
感谢我的朋友,deepin
助和鼓励。感谢我所有的NSFocus安全研究小组的朋友(tt、yuange、security@
nsfocus.com ... ...),是他们容忍我在这个非正业上花费时间。
主要支持人员(字母顺序) --
Andrew Gierth
backend
Casper Dik
deepin
scz
suxm
tt
简介 --
这份文档不是FAQ(Frequently Answered Question),不少问题属于FUQ(Freque-
ntly Unanswered Question)。换句话说,不一定是最常见的编程、应用问答,很可
能其中的答案尚是一个构思,还没有成为现实,又或者根本是个错误的思想火花。但
是,她的确在试图回答一些很有意义的问题,让更多的Unix/C程序员、系统管理员共
享彼此的智慧,那是三十年前无数前辈精英做到过的,也是我们正试图做到的。
Q -- Question
A -- Answer
D -- Discuss
声明 --
永久拒绝任何商业性质的转载、摘录、引用。在不对所有文字做任何修正的前提
下,允许一切教育性质的转载、摘录、引用,无须提前知会维护者(就是me,faint)。
一旦出现需要修正文字的情况,只能通过维护者修正。维护者会在下一次版本升级过
程中正式增加这种修正,保留提供修正者应有信息。同时意味着提供修正者永久自愿
放弃商业性质的所有权益。不接受这种条件的提供修正者,务必提前知会维护者,此
类修正将不出现在下一次版本升级中。
文中所附各种源代码,在严格意义上可能存在版权问题,所以事实上这份文档带
有"半地下"性质,使用者务必自己小心卷入此类纠纷。
文中技术可能涉及未公开的、未文档化的、非规范的编程、应用接口,文档提供
的重在思想,而不保证是正确、高效、唯一的解答。
维护者不对文中任何技术引起的任何灾难性后果负任何法律上的、道义上的责任。
Ok, Let‘s go.
辅助说明 --
2002-03-06 12:14
辅助说明只在"外发版"中存在,稍微解释一下。
一直没有单独出一份完整的,原因很多。如果搁在1995/1996/1997时的CERNET,
这些原因都不成为原因,现在成为原因。不想多说为什么,明白的自然明白,不
明白的当我白痴好了,反正别问我。
出于"声明"中的某些理由,不能在单份完整文档中附带可能会带来麻烦的文字、
代码,比如Solaris libproc编程接口。但是,在散篇中你能找到它们。如果你
愿意,可以自己将散篇收回到该文档中,这将与我无关。一切索要残缺部分的邮
件盖不回复。
本份文档的绝大多数内容在"中国教育科研网华南地区网络中心BBS"(bbs.gznet.
edu.cn)的Solaris版发布过了,包括下面处理掉的目录列表。是该版前版主CPU
师兄当年的风范促使我开始整理这份文档的,当还昔日指教之情谊。
该份文档"允许一切教育性质的自由转载、摘录、引用,无须提前知会维护者"。
我也只是义务维护一下,不对本文档拥有任何权益。如果不幸潜在拥有而践踏了
某种信念,在你看到该辅助说明的同时,我将自动放弃这种潜在可能拥有的权益。
同时意味着一切因本文档带来的麻烦,将由你个人承担。
既然来自Unix共享传统文化圈,就让它彻底回到Unix共享传统文化圈中去吧。
欢迎一切建设性的、非索要性质的Email交流。
--------------------------------------------------------------------------
目录
0. Unix/C传奇问题
0.1 Dennis Ritchie 和 Ken Thompson
0.2 W. Richard Stevens 之死
1. 系统管理配置问题
1.1 如何给SUN工作站增加eeprom硬件口令保护
1.2 如何增加交换空间
1.3 为什么我不能在/home目录下创建子目录
1.4 如何改变一台主机的locale
1.5 Solaris 7自动注销
1.6 一个目录拥有setgid设置,怎么理解
1.7 非Sun Console上有无等价Stop-A的按键
1.8 如何让一个用户只能ftp而无法telnet
1.9
1.10 为什么Sun工作站非要输入boot命令才能启动
1.11 如何让Solaris识别新增加的硬件
1.12
2. 堆栈相关问题
2.1 如何理解pstack的输出信息
2.2
2.3 Solaris中如何获取一个C程序的调用栈回溯
2.4 如何编程获取栈底地址
2.5 如何得到一个运行中进程的内存映像
2.6 调试器如何工作的
2.7 x86/Linux上如何处理SIGFPE信号
3. -lelf、-lkvm、-lkstat相关问题
3.1 如何判断可执行文件是否携带了调试信息
3.2 mprotect如何用
3.3 mmap如何用
3.4 getrusage如何用
3.5 setitimer如何用
4. 系统资源相关问题
4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况
4.2 Solaris下如何获知CPU速率
4.3 如何编程获取Solaris系统当前内存大小
5. 块设备相关问题
5.1 CDROM设备究竟在哪里
5.2 如何弹出光驱
5.3 如何利用超级块进行恢复工作
5.4 Solaris Root口令忘记了
5.5 如何使用fmthard
5.6 如何从光盘恢复Solaris 7的引导扇区
5.7 Solaris支持类似微软autorun.inf文件的功能吗
5.8 如何修改/dev/null的属性
5.9
5.10 如何自己制作Solaris启动软盘
5.11 x86/Solaris如何访问FAT32分区
6. /etc/system可调资源限制
6.1 Solaris下如何限制每个用户可拥有的最大进程数
6.2 如何配置系统使之支持更多的伪终端
6.3 如何增加每个进程可打开文件句柄数
6.4
6.5 做了setuid()这类调用的程序如何产生core dump
6.6 消息队列调整
7. DNS相关问题
7.1 如何进行DNS区传输
7.2 如何获知权威名字服务器
7.3 如何配置DNS的委托解析
7.4 如何获知BIND的版本号
7.5 Solaris/FreeBSD/Linux如何指定域名解析的顺序
8. Solaris内核编程相关问题
8.1 Solaris内核模块中如何getcwd
8.2
8.3 如何避免一个套接字进入TIME_WAIT状态
8.4 结构在优化编译中的对齐问题
8.5
8.6 如何得到非局部变量列表
8.7
8.8 如何单独获得Solaris编译环境
8.9 如何获取Solaris内核可调参数列表
8.10
8.11 如何页边界对齐式分配内存
8.12
8.13 compile()和step()怎么用
8.14
8.15
8.16
8.17
9. 图形界面相关问题
9.1 如何避免进入Solaris的图形界面
9.2 Solaris 7的锁屏
9.3 如何调整键盘重复率
9.4 如何拔掉键盘继续运行Solaris
9.5 Solaris下如何设置显卡分辨率
9.6 Solaris下如何设置显示刷新率
10. 网卡相关问题
10.1 如何在程序中获取本机MAC地址
10.2 如何在Sun工作站上安装3块网卡
10.3 如何在Solaris x86上安装网卡驱动
10.4 Solaris 单网卡多IP(以太网卡别名)
10.5 如何修改主机名(hostname)
10.6 SPARC/Solaris 2.5/2.6/7/8下如何设置网卡100Mb全双工
10.7 Unix/Linux/BSD如何对抗ARP欺骗攻击
10.8
10.9
10.10
10.11 x86/Solaris如何强制设定网卡速率
10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed
10.13
10.14 traceroute是怎么实现的
11. package相关问题
11.1 Solaris下如何将二进制软件包安装到指定目标路径下
11.2 Solaris下如何自己定制二进制安装包
11.3 如何恢复/usr/bin/su的缺省安装属性
11.4 如何获知指定包与其他包之间的依赖关系
11.5 Linux中如何知道ifconfig属于哪个包
11.6 Solaris下如何知道某包中有哪些文件
12. 日志相关问题
12.1
12.2
12.3 如何关闭cron的日志
12.4
13. 进程相关问题
13.1 如何根据进程名获得PID
13.2
13.3
13.4 Solaris 7/8下ps输出中的问号
13.5
13.6
13.7 给定一个PID,如何知道它对应一个运行中的进程
13.8 Unix/Linux编程中所谓"僵尸进程"指什么
13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页
13.10 Solaris下如何知道哪个进程使用了哪个端口
13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数
14. 一些小工具的使用
14.1
14.2
14.3 只在本地文件系统上查找
14.4
15. 32-bit/64-bit相关问题
15.1 Solaris下如何识别当前内核版本
15.2 如何启动Solaris 32-bit/64-bit内核
15.3 gcc支持64-bit编译吗
15.4 Solaris启动时内核文件找不到了
15.5 64-bit驱动程序无法在8下关联,但在7下工作正常
16. 库相关问题
16.1 在Solaris 7下编写网络程序需要链接哪些库
16.2
16.3 链接过程中库的顺序
16.4
16.5
16.6 /usr/lib/ld.so.1损坏或丢失
16.7
16.8
16.9 Solaris 8下如何配置运行时链接环境
17. 文件查看问题
17.1 如何直接查看man文件
17.2 .tex文件怎么读
17.3 Solaris下怎么看.ps文件
18. 补丁相关问题
18.1 如何根据补丁号从Sun主站下载补丁
18.2
18.3
18.4 给Solaris 2.6安装推荐补丁集
18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁
18.6 如何安装补丁
19. 终端相关问题
19.1 如何使Backspace键做删除操作,而不是显示^H
19.2
19.3 如何清空stdin的缓冲
19.4 Linux Console下一按错键就叫,怎么关
20. shell script问题
20.1 如何获取一个字符串的长度
20.2 读超时自动使用缺省值
20.3
20.4 BASH中如何得到一个字符串的子串
20.5
20.6
20.7
20.8 使用tr命令加密文件
20.9 有哪些命令用于查找定位
20.10
20.11 如何将大写文件名转换为小写文件名
21. FreeBSD相关问题
21.1
21.2 如何将一个512字节的文件写入主引导扇区
21.3
21.4
21.5
21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel
21.7 x86/FreeBSD下如何设置路由
21.8
21.9 什么是locale
21.10 用cvsup安装vim
21.11 FreeBSD下vi输入中文会显示\x??\x??
21.12
21.13
21.14
21.15 UDMA ICRC error是什么意思
21.16 Limiting closed port RST response什么意思
21.17
21.18
21.19
21.20
22. Linux Kernel Programming
22.1 直接访问内存[显存]地址
22.2
23. Linux相关问题
23.1
--------------------------------------------------------------------------
0. Unix/C传奇问题
0.1 Dennis Ritchie 和 Ken Thompson
Q: 我想知道他们,为什么大家不断提到这两个名字?
A: All of Unix Programmers
我们也想知道,:-P
1969年Dennis Ritchie 和 Ken Thompson在贝尔实验室创造性地发明了Unix操作系统,
为此1983年他们获得了图灵奖。
尽管Ritchie是C程序设计语言的发明者,但是他最喜欢的编程语言是Alef。而
Thompson是一名业余飞行员,曾到莫斯科驾驶过米格-29。
欢迎访问
http://cm.bell-labs.com/who/dmr/
http://cm.bell-labs.com/who/ken/
0.2 W. Richard Stevens 之死
Q: David Johns
我是他的崇拜者,用www.google.com搜索他的讣告,但这份讣告没有提及死因,有人
知道吗?
真地仅仅是英年早逝吗?
A: Nithyanandham
他死于1999/09/01,家人不想让别人知道死因。讣告位于
http://www.azstarnet.com/clips/richard_stevens.html
A: joe broz
似乎是一场攀岩事故,或者滑雪事故,我不确认。
1. 系统管理配置问题
1.1 如何给SUN工作站增加eeprom硬件口令保护
A: scz
man -s 1M eeprom了解细节,要求当前是root身份
# /usr/sbin/eeprom (显示当前eeprom配置)
# /usr/sbin/eeprom security-mode=full ( 可选的有command, full, none)
此时进入交互式设置口令过程,总共输入两次,如果两次口令输入不一致,则本次设
置作废。成功设置之后除了go命令之外的其他ok状态下命令均需要口令,包括boot命
令。
设置成command时,同样进入交互式口令输入过程。此时,除了boot和go命令之外的
其他ok状态下命令均需要口令。注意,如果仅仅输入boot命令,不需要口令,一旦
boot命令后面带了参数,比如boot cdrom -s,同样需要输入口令。
如果设置成none(缺省设置),表示去掉这种口令保护。
# /usr/sbin/eeprom security-password= (等号后面无其他字符,直接回车)
如果想改变前面设置的口令,用这条命令,同样是交互式输入过程。
# /usr/sbin/eeprom security-#badlogins=3 (缺省是0)
设置口令输入尝试次数。
警告:如果设置了eeprom硬件保护口令而又忘记,会带来很多麻烦,务必小心。
一个可行的设置办法是,安全模式设置到command而不是full,这样至少可以正常启
动系统。于是只要记得root口令或者还有其他机会获得root权限(缓冲区溢出?),就
可以通过设置安全模式为none而挽救回来。
但是如果设置成full模式却忘记了eeprom口令,我想首先应该打电话给SUN的技术支
持。如果出于某种理由你不想这样做,我不确认eeprom是否可以热插拔,先用一个无
口令保护的eeprom启动系统,然后热插拔换上那个有口令保护的eeprom,然后用root
权限抹去eeprom口令。
1.2 如何增加交换空间
A: WT
你无法改变分区大小,但是可以增加/删除交换文件,效果类似交换分区。下列命令
在根目录下创建一个500MB的交换文件,名为swapfile
# mkfile 500m /swapfile
下列命令将使之生效
# swap -a /swapfile
现在你有了额外的500MB交换空间,为了每次重启后依旧有效,编辑/etc/vfstab文件
增加如下行
/swapfile - - swap - no -
# swap -l
这里"-l"意味着"list",显示所有交换空间。仔细阅读"swap"和"mkfile"的手册页。
1.3 为什么我不能在/home目录下创建子目录
Q: Solaris 7下,root身份,当我试图在/home目录下创建子目录时,系统拒绝,为
什么?
A: mohansundarraj
如果/etc/rc2.d/S74autofs脚本中automount(1M)守护进程已经mount了/home,就是
这种现象,而这还是缺省安装后的情形。可以
# /etc/init.d/autofs stop
# umount /home
然后你就可以用root身份在/home下创建子目录,增加文件了。为了永久取消autofs
特性,可以将/etc/rc2.d/S74autofs脚本改名,并注释掉/etc/auto_home、
/etc/auto_master两个文件中的入口点。
SPARC/Solaris的缺省用户主目录是/export/home,而不是/home。
1.4 如何改变一台主机的locale
Q: 一台SPARC/Solaris 8运行在US locale环境中,现在我们想让它运行在
IE(Ireland) locale环境中,以便可以使用欧洲日期格式,怎么办?
A: Sharad Ramachandran
运行sys-unconfig,在此之前请man -s 1M sys-unconfig,:-)
A: chad schrock
天啊,为了拍死一只苍蝇,你要引爆原子弹吗?
只需要做如下操作,在你的.cshrc/.profile/.bashrc等启动脚本中设置$LANG环境变
量的值为en_UK,注销,重新登录即可。为了使这个设置全局有效,修改
/etc/default/init文件,LANG=en_UK,重启动。
--------------------------------------------------------------------------
# @(#)init.dfl 1.2 92/11/26
#
# This file is /etc/default/init. /etc/TIMEZONE is a symlink to this file.
# This file looks like a shell script, but it is not. To maintain
# compatibility with old versions of /etc/TIMEZONE, some shell constructs
# (i.e., export commands) are allowed in this file, but are ignored.
#
# Lines of this file should be of the form VAR=value, where VAR is one of
# TZ, LANG, or any of the LC_* environment variables.
#
TZ=GMT+8
LANG=zh.GBK
--------------------------------------------------------------------------
参看locale(1)和locale(5),了解更多关于locale的信息。运行"locale -a",查看
当前系统所支持的所有locale。
A: Sun Microsystems 2001-06-12
有三种方式改变locale。首先用"locale -a"命令确认系统中已安装的locale
1) 从CDE登录屏幕上修改locale
选择 options -> languages -> choose the new locale
注意,如果登录用户的初始化文件中有不同的locale设置,将优先于系统全局locale
设置。
2) 临时设置locale(shell相关的)
ksh : LANG=
sh : LANG=
export LANG
csh : setenv LANG
bash: export LANG=en_US(zh.GBK)
3) vi /etc/default/init
增加如下内容
LANG=
LC_ALL=
重启系统。
运行"locale"命令确认改变生效。
如果你希望使用的locale并未安装,参看如下文档安装locale
Solaris 8 : <
Solaris 7 : <
Solaris 2.6: <
D: scz
SPARC/Solaris 2.5下,为了在vi中正确看到中文需要设置环境变量
sh
LANG=C;export LANG
LC_CTYPE=iso_8859_1;export LC_CTYPE
csh
setenv LANG zh
关于设置LANG这个环境变量涉及到/usr/lib/locale下的目录权限。
1.5 Solaris 7自动注销
Q: 怎样设置才能30秒后自动注销
A: shridhara
不幸的是,Solaris对此没有什么好的支持。如果正在使用telnet会话,或许可以考
虑"logout"变量,参看telnet的手册页。一个变通的办法,使用K-Shell,它支持
TMOUT变量,用于指定非活动时限(以秒为单位)。比如,如果一个shell会话3分钟内
不活动,则终止这个shell会话
$ TMOUT=180;export TMOUT
可以在用户的.profile文件中放置该行。缺点是你只能使用ksh。
D: scz
vi /etc/default/login
# TIMEOUT sets the number of seconds (between 0 and 900) to wait before
# abandoning a login session.
#
TIMEOUT=180
这里的超时设置针对登录过程,而不是登录成功后的shell会话超时设置。
1.6 一个目录拥有setgid设置,怎么理解
Q: 对一个目录做了setgid设置,可我并没有发现这和正常情况有什么区别
A: John Riddoch
在这种目录下创建新文件时将采用setgid设置对应的属组,比如
$ ls -ld b
drwxrws--- 2 jr group 512 Mar 14 17:13 b/
$ touch b/a
$ ls -l b/a
-rw------- 1 jr group 0 Mar 14 17:13 b/a
$ id
uid=178(jr) gid=10(staff)
jr的缺省组是staff,而现在b/a文件属组是group。
D: 小四
SPARC/Solaris 7下测试
如果目录拥有SGID设置,那么该目录下新创建的文件将继承该目录的属组,而不是创
建者所对应的GID。
[root@ /export/home/scz]> id
uid=0(root) gid=1(other) <-- 注意当前用户的属组
[root@ /export/home/scz]> mkdir groupsgid
[root@ /export/home/scz]> ls -ld groupsgid
drwxr-xr-x root other groupsgid/
[root@ /export/home/scz]> chown scz:users groupsgid
[root@ /export/home/scz]> chmod g+s groupsgid
[root@ /export/home/scz]> ls -ld groupsgid
drwxr-sr-x scz users groupsgid/ <-- 目录拥有SGID设置
[root@ /export/home/scz]> cd groupsgid/
[root@ /export/home/scz/groupsgid]> touch scz_0
[root@ /export/home/scz/groupsgid]> ls -l scz_0
-rw-r--r-- root users scz_0 <-- 注意属组变化
[root@ /export/home/scz/groupsgid]> chmod g-s ../groupsgid/
[root@ /export/home/scz/groupsgid]> ls -ld ../groupsgid/
drwxr-xr-x scz users ../groupsgid/
[root@ /export/home/scz/groupsgid]> touch scz_1
[root@ /export/home/scz/groupsgid]> ls -l scz_1
-rw-r--r-- root other scz_1 <-- 注意属组变化
[root@ /export/home/scz/groupsgid]>
1.7 非Sun Console上有无等价Stop-A的按键
A: neomilev
如果是便携机,尝试alt/break 或者 ctrl/break。如果是vt100终端,尝试F11 或者
break
1.8 如何让一个用户只能ftp而无法telnet
A: 小四
修改该用户在/etc/passwd中的shell为/bin/false,在/etc/shells文件中增加
/bin/false,此时,该用户只能ftp,telnet失败。
1.10 为什么Sun工作站非要输入boot命令才能启动
Q: 我有台Sun工作站,每次开机后停在ok状态下,需要手工输入boot命令才能启动,
现在想避免这种效果,怎么办
A: /usr/sbin/eeprom auto-boot?=true
/usr/sbin/eeprom auto-boot? <-- 查询
A: dengdai@SMTH
进入OBP状态
ok setenv auto-boot? true
ok setenv boot-device disk
反之
ok setenv auto-boot? false
1.11 如何让Solaris识别新增加的硬件
Q: 比如新增加了网卡、硬盘、光驱什么的,如何让Solaris意识到这种增加
A: spp(低音炮) & suxm
有三种办法
a. Stop-A进入OBP状态,输入boot -r
b. sync(重复);reboot -- -r
c. touch /reconfigure;sync(重复);reboot
参看reboot(1M)、boot(1M)、eeprom(1M)、kernel(1M)、cfgadm(1M)、psradm(1M)手
册页
Q: 我新增加了一块硬盘,不想boot -r而立即生效,怎么办
A: 老大
直接将第二块硬盘接上去,然后顺序执行如下命令,不用重新启动机器
modunload -i 0
drvconfig(1M)
devlinks(1M)
disks(1M)
如果需要重新格式化、分区、创建文件系统,就继续执行
format(1M)
newfs(1M)
2. 堆栈相关问题
2.1 如何理解pstack的输出信息
Q: 080603a7 main (1, 80479b8, 80479c0) + d53
结尾的d53是什么
A: Roger A. Faulkner
在代码段绝对地址0x080603a7处,main()调用了一个函数,0x080603a7正是
main + 0xd53,换句话说,从main()函数开始的0xd53偏移处。
2.3 Solaris中如何获取一个C程序的调用栈回溯
Q: 我想在Solaris 2.6极其后续版本上获取一个C程序的调用栈回溯,类似如下输出
(10) 0x00045e08 integ + 0x408 [./two_brn.e]
(11) 0x0006468c trajcem + 0x128 [./two_brn.e]
(12) 0x00055490 fly_traj + 0xf58 [./two_brn.e]
(13) 0x0004052c top_level + 0x14 [./two_brn.e]
(14) 0x000567e4 _start + 0x34 [./two_brn.e]
这样我就可以知道当程序崩溃、死锁的时候代码执行到了何处。在HP-UX和IRIX上
可以利用U_STACK_TRACE()和trace_back_stack_and_print(),Solaris上呢?
Q: 有没有办法显示当前堆栈中的数据(GNU/Linux系统)?我希望自己的异常处理程序
在进程结束前dump整个栈区(stack),以便观察到栈顶是什么函数。对于调试意想
不到的运行时错误而言,这很重要。
A: Bjorn Reese
用/usr/proc/bin/pstack [-F]
参看这个例子代码,http://home1.stofanet.dk/breese/debug/debug.tar.gz
Q: is there a way to access call stack information at run time from within
a program? i‘ve been maintaining my own crude stack using __FUNCTION__
and linked lists but can‘t help but think there‘s gotta be a better
way...
A: Nate Eldredge
这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数,
参看
注意,你所使用的办法可能是唯一能够保证跨平台使用的
A: Andrew Gabriel
下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那
么这个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,
好像Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时
间了。
/*
* Produce a stack trace for Solaris systems.
*
* Copyright (C) 1995-1998 Andrew Gabriel
* Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
*
*/
/* ......................................................................... */
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(sparc) || defined(__sparc)
#define FLUSHWIN() asm("ta 3");
#define FRAME_PTR_INDEX 1
#define SKIP_FRAMES 0
#endif
#if defined(i386) || defined(__i386)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 3
#define SKIP_FRAMES 1
#endif
#if defined(ppc) || defined(__ppc)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 0
#define SKIP_FRAMES 2
#endif
/* ......................................................................... */
static void print_address ( void * pc )
{
Dl_info info;
if ( dladdr( pc, &info ) == 0 )
{
/* not found */
fprintf( stderr, "*** %s:0x%x\n", "??", ( unsigned int )pc );
}
else
{
/* found */
fprintf( stderr, "*** %s:%s+0x%x\n", info.dli_fname, info.dli_sname,
( unsigned int )pc - ( unsigned int )info.dli_saddr );
}
return;
} /* end of print_address */
/* ......................................................................... */
static int validaddr ( void * addr )
{
static long pagemask = -1;
char c;
if ( pagemask == -1 )
{
pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 );
}
addr = ( void * )( ( long )addr & pagemask );
if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM )
{
return 0; /* invalid */
}
else
{
return 1; /* valid */
}
} /* end of validaddr */
/* ......................................................................... */
/*
* this function walks up call stack, calling print_addess
* once for each stack frame, passing the pc as the argument.
*/
static void print_stack ( void )
{
struct frame * sp;
jmp_buf env;
int i;
int * iptr;
FLUSHWIN();
setjmp( env );
iptr = ( int * )env;
sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ];
for ( i = 0; i < SKIP_FRAMES && sp; i++ )
{
if ( !validaddr( sp ) || !validaddr( &sp->fr_savpc ) )
{
fprintf( stderr, "***[stack pointer corrupt]\n" );
return;
}
sp = ( struct frame * )sp->fr_savfp;
}
i = 100; /* looping check */
while ( validaddr( sp ) && validaddr( &sp->fr_savpc ) && sp->fr_savpc && --i )
{
print_address( ( void * )sp->fr_savpc );
sp = ( struct frame * )sp->fr_savfp;
}
} /* end of print_stack */
/* ......................................................................... */
void backtrace( void )
{
fprintf( stderr, "***backtrace...\n" );
print_stack();
fprintf( stderr, "***backtrace ends\n" );
}
/* ......................................................................... */
2.4 如何编程获取栈底地址
Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序
获取这个栈底地址。
A: tt
假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址
x86/Linux 栈底是0xc0000000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 2.6 栈底是0xf0000000( 栈底往低地址的4个字节总是零 )
x86/FreeBSD 栈底是0xbfc00000( 栈底往低地址的4个字节总是零 )
x86/NetBSD 1.5 栈底是0xbfbfe000
x86/OpenBSD 2.8 栈底是0xdfbfe000
D: jonah
对于NetBSD 1.5,栈底是0xbfc00000。根据源码,最高用户地址是0xbfbfe000,因为
最后4MB(2^22)的最后两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再
使用这块内存。因此,0xbfbfe000才是真正的栈底。
tt在OpenBSD 2.8上测试结果,栈底是0xdfbfe000,注意和NetBSD 1.5相差很大。
A: tt
--------------------------------------------------------------------------
/*
* gcc -Wall -O3 -o gstack gstack.c
*
* A simple example to get the current stack bottom address
* warning3
* 2001-06-01
*
* Modified by scz
* 2001-06-02
*/
#include
#include
#include
#include
#include
typedef void Sigfunc ( int ); /* for signal handlers */
Sigfunc * signal ( int signo, Sigfunc * func );
static Sigfunc * Signal ( int signo, Sigfunc * func );
static char * get_stack_bottom ( void );
static void segfault ( int signo );
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump = 0;
static Sigfunc *seg_handler;
static Sigfunc *bus_handler; /* for xxxBSD */
Sigfunc * signal ( int signo, Sigfunc * func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() function */
{
Sigfunc * sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static char * get_stack_bottom ( void )
{
volatile char *c; /* for autovar, must be volatile */
seg_handler = Signal( SIGSEGV, segfault );
bus_handler = Signal( SIGBUS, segfault );
c = ( char * )&c;
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
{
Signal( SIGSEGV, seg_handler );
Signal( SIGBUS, bus_handler );
return( ( char * )c );
}
canjump = 1; /* now sigsetjump() is OK */
while ( 1 )
{
*c = *c;
c++;
}
return( NULL );
} /* end of get_stack_bottom */
static void segfault ( int signo )
{
if ( canjump == 0 )
{
return; /* unexpected signal, ignore */
}
canjump = 0;
siglongjmp( jmpbuf, signo ); /* jump back to main, don‘t return */
} /* end of segfault */
int main ( int argc, char * argv[] )
{
fprintf( stderr, "Current stack bottom is at 0x%p\n", get_stack_bottom() );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
D: scz
W. Richard Stevens在<
介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。
这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV
信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。
tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD,
NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV
信号。
非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号
句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此
时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句
柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了
保存恢复,有些系统没有做。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函
数。下面来自SPARC/Solaris 7的setjmp(3C)
--------------------------------------------------------------------------
#include
int setjmp ( jmp_buf env );
int sigsetjmp ( sigjmp_buf env, int savemask );
void longjmp ( jmp_buf env, int val );
void siglongjmp ( sigjmp_buf env, int val );
--------------------------------------------------------------------------
如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回
来的时候从env中恢复信号屏蔽字。
数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有
虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是
与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。
在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也
保持不变。
无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom()
中声明c为volatile变量。
注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、
SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指
令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一
次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用
长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果
在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。
D: scz
在x86/Linux系统中用如下命令可以确定栈区所在
# cat /proc/1/maps <-- 观察1号进程init
... ...
bfffe000-c0000000 rwxp fffff000 00:00 0
#
在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在
# /usr/proc/bin/pmap 1 <-- 观察1号进程init
... ...
FFBEC000 16K read/write/exec [ stack ]
#
16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000
与前面tt介绍的
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
相符合。
此外,在SPARC/Solaris 7下,可以这样验证之
# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|_userlimit"
[7015] |0x0000100546f8|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit
[8051] |0x000010054700|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit32
# echo "_userlimit /J" | adb -k /dev/ksyms /dev/mem
physmem 3b72
_userlimit:
_userlimit: ffffffff80000000
# skd64 0x000010054700 8
byteArray [ 8 bytes ] ---->
0000000000000000 00 00 00 00 FF BF 00 00
# ~~~~~~~~~~~ 对于32-bit应用程序来说,这是用户
空间上限
如果编译64-bit应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000
# /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o gstack gstack.c
# ./gstack
Current stack bottom is at 0xffffffff80000000
#
对于SPARC/Solaris 2.6 32-bit kernel mode
# echo "_userlimit /X" | adb -k /dev/ksyms /dev/mem
physmem 3d24
_userlimit:
_userlimit: f0000000
#
2.5 如何得到一个运行中进程的内存映像
A: Sun Microsystems 1998-03-30
有些时候必须得到一个运行中进程的内存映像而不能停止该进程,Solaris系统了这
样的工具,gcore为运行中进程创建一个core文件。假设我的bash进程号是5347
# gcore 5347
gcore: core.5347 dumped
# file core.5347
core.5347: ELF 32-位 MSB core文件 SPARC 版本 1,来自‘bash‘
#
注意,只能获取属主是你自己的进程的内存映像,除非你是root。
2.6 调试器如何工作的
Q: 我想在一个自己编写的程序中单步运行另外一个程序,换句话说,那是一个调试
器,该如何做?
A: Erik de Castro Lopo
这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我
不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN
和BSD 4.3都支持它。
为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用:
ptrace( PTRACE_TRACEME, 0, 0, 0 );
接下来调用exec()家族的函数执行你最终企图跟踪的程序。
为了单步进入子进程,在父进程中调用:
ptrace( PTRACE_SINGLESTEP, 0, 0, 0 );
还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。
GDB的源代码足以回答这个问题。
2.7 x86/Linux上如何处理SIGFPE信号
Q: 参看如下程序
--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c
*
* 注意与下面的编译效果进行对比,去掉优化开关-O3
*
* gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c
*/
#include
#include
#include
#include
#include
#include
/*
* for signal handlers
*/
typedef void Sigfunc ( int );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void on_fpe ( int signo );
Sigfunc * signal ( int signo, Sigfunc *func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func )
{
Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void on_fpe ( int signo )
{
fprintf( stderr, "here is on_fpe\n" );
return;
} /* end of on_fpe */
int main ( int argc, char * argv[] )
{
unsigned int i;
Signal( SIGFPE, on_fpe );
i = 51211314 / 0;
/*
* 另外,增加这行后,再次对比有-O3和无-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
有-O3、无-O3,以及有无最后那条fprintf()语句,效果上有差别,自行对比。如果
输出"here is on_fpe",则会发现永不停止。
D: 小四
为了便于讨论,约定两个名词,中断和异常。这里中断指最常规的中断,比如int指
令带来的软中断。异常的典型代表有除0错。区别在于,发生异常时,x86架构上CPU
将当前EIP(指向引发异常的指令)压栈,发生中断时,x86架构上CPU将当前EIP的后一
个地址(指向引发中断的指令的后一条指令)压栈。在异常处理代码中,如果认为能够
从灾难中恢复,可以不修改被压栈的EIP,从而返回到引发异常的指令处。更多细节
请查看Intel手册。
这些是从前DOS下残留的汇编知识,不过也快忘光了,刚才又找元宝宝确认了一下。
在上述代码中,on_fpe()直接返回了,导致再次触发异常,所以无休止输出。事实上
在所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的
时候又碰上过。正确的做法是,利用远跳转转移,让开引发异常的指令。
代码修改如下
--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c
*
* 注意与下面的编译效果进行对比,去掉优化开关-O3
*
* gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.c
*/
#include
#include
#include
#include
#include
#include
/*
* for signal handlers
*/
typedef void Sigfunc ( int );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void on_fpe ( int signo );
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump = 0;
Sigfunc * signal ( int signo, Sigfunc *func )
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
}
else
{
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func )
{
Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void on_fpe ( int signo )
{
if ( canjump == 0 )
{
return; /* unexpected signal, ignore */
}
canjump = 0;
fprintf( stderr, "here is on_fpe\n" );
siglongjmp( jmpbuf, signo ); /* jump back to main, don‘t return */
return;
} /* end of on_fpe */
int main ( int argc, char * argv[] )
{
unsigned int i;
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
{
fprintf( stderr, "c u later\n" );
return( EXIT_SUCCESS );
}
/*
* now sigsetjump() is OK
*/
canjump = 1;
Signal( SIGFPE, on_fpe );
i = 51211314 / 0;
/*
* 另外,增加这行后,再次对比有-O3和无-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------
关于-O3的讨论,对gcc编译器熟悉的朋友请继续,呵,我对Linux下的这此东西,实
在缺乏兴趣。
3. -lelf、-lkvm、-lkstat相关问题
3.1 如何判断可执行文件是否携带了调试信息
Q: 某些时候需要知道编译可执行文件时是否携带了调试信息(比如是否指定了-g编译
选项)。检查可执行文件中是否包含".stab" elf section,".stab" section用于
保存相关调试信息。
A: Sun Microsystems 2000-05-15
下面这个脚本演示如何判断可执行文件是否携带调试信息
--------------------------------------------------------------------------
#! /bin/sh
#
# Script that test whether or not a given file has been built for
# debug (-g option specified in the compilation)
if [ $# -le 0 ]
then
echo "Usage: $1 filename"
exit 1
fi
if [ ! -f $1 ]
then
echo "File $1 does not exist"
exit 1
fi
/usr/ccs/bin/dump -hv $1 | /bin/egrep -s ‘.stab$‘
if [ $? -eq 0 ]
then
echo "File ‘$1‘ has been built for debug"
exit 0
else
echo "File ‘$1‘ has not been built for debug"
exit 1
fi
--------------------------------------------------------------------------
如果对ELF文件格式不熟悉,理解上述代码可能有点困难,参看
http://www.digibel.org/~tompy/hacking/elf.txt,这是1.1版的ELF文件格式规范。
3.2 mprotect如何用
A: 小四
# truss prtconf 2>&1 | grep sysconf
sysconfig(_CONFIG_PAGESIZE) = 8192
sysconfig(_CONFIG_PHYS_PAGES) = 16384
#
由此可知当前系统页尺寸是8192字节。
--------------------------------------------------------------------------
/*
* gcc -Wall -g -ggdb -static -o mtest mtest.c
*/
#include
#include
#include
#include
int main ( int argc, char * argv[] )
{
char *buf;
char c;
/*
* 分配一块内存,拥有缺省的rw-保护
*/
buf = ( char * )malloc( 1024 + 8191 );
if ( !buf )
{
perror( "malloc" );
exit( errno );
}
/*
* Align to a multiple of PAGESIZE, assumed to be a power of two
*/
buf = ( char * )( ( ( unsigned int )buf + 8191 ) & ~8191 );
c = buf[77];
buf[77] = c;
printf( "ok\n" );
/*
* Mark the buffer read-only.
*
* 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数
*/
if ( mprotect( buf, 1024, PROT_READ ) )
{
perror( "\nmprotect" );
exit( errno );
}
c = buf[77];
/*
* Write error, program dies on SIGSEGV
*/
buf[77] = c;
exit( 0 );
} /* end of main */
--------------------------------------------------------------------------
$ ./mtest
ok
段错误 (core dumped) <-- 内存保护起作用了
$
3.3 mmap如何用
A: 小四
下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。
--------------------------------------------------------------------------
/*
* gcc -Wall -O3 -o copy_mmap copy_mmap.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PERMS 0600
int main ( int argc, char * argv[] )
{
int src, dst;
void *sm, *dm;
struct stat statbuf;
if ( argc != 3 )
{
fprintf( stderr, " Usage: %s
linux宝库/编程技术/Unix编程/应用问答中文版
Unix编程/应用问答中文版(转) - lovechen508的专栏 - CSDNBlog
linux宝库/编程技术/各种语言的介绍
学习linux/unix编程方法
linux宝库/编程技术/用Gtk 开发Linux上的GUI应用软件
学习linux/unix编程方法的建议
学习linux/unix编程方法的建议
linux宝库/编程技术/创建和使用库:静态、共享和动态
linux宝库/python/最小的Zope编程 How-to
[ 永远的UNIX > Linux下的多线程编程 ]
UNIX编程艺术
UNIX 编程资料 (转)
编程技术
Linux系统下C语言编程基础知识介绍 - Linux/Unix社区 / 实用资料发布区
Linux串口编程分析
Linux 内核编程风格
Linux串口编程分析
linux内核模块编程
Linux Unicode 编程
linux内核模块编程
linux socket 编程
Linux下Socket编程
Linux 下串口编程
CSDN技术中心 Linux Shell编程学习笔记