Unix编程/应用问答中文版
名称 -- Unix编程/应用问答中文版
版本 -- 0.04 ( 2003-10-09 外发版 )
维护 -- 小四 <scz@nsfocus.com>
主页 -- http://www.nsfocus.com
创建 -- 2001-02-05 13:49
更新 -- 2004-02-05 09:23

感谢 --

感谢C语言的发明者、Unix操作系统的发明者、感谢全世界C程序员创造的Unix共
享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。

感谢所有NSFOCUS安全研究小组(security@nsfocus.com)的朋友。

主要支持人员(字母顺序) --

Andrew Gierth <andrew@erlenstar.demon.co.uk>
backend <backend@nsfocus.com>
Casper H.S. Dik <Casper.Dik@Sun.COM>
deepin <deepin@nsfocus.com>
jbtzhm <jbtzhm@nsfocus.com>
scz <scz@nsfocus.com>
suxm <suxm@nsfocus.com>
tt <warning3@nsfocus.com>

简介 --

这份文档不是FAQ(Frequently Answered Question),不少问题属于FUQ(Freque-
ntly Unanswered Question)。换句话说,不一定是最常见的编程、应用问答,很可
能其中的答案尚是一个构思,还没有成为现实,又或者根本是个错误的思想火花。但
是,她的确在试图回答一些很有意义的问题,让更多的Unix/C程序员、系统管理员共
享彼此的智慧,那是三十年前无数前辈精英做到过的,也是我们正试图做到的。

Q -- Question
A -- Answer
D -- Discuss

声明 --

永久拒绝任何商业性质的转载、摘录、引用。在不对所有文字做任何修正的前提
下,允许一切教育性质的转载、摘录、引用,无须提前知会维护者(就是me,faint)。
一旦出现需要修正文字的情况,只能通过维护者修正。维护者会在下一次版本升级过
程中正式增加这种修正,保留提供修正者应有信息。同时意味着提供修正者永久自愿
放弃商业性质的所有权益。不接受这种条件的提供修正者,务必提前知会维护者,此
类修正将不出现在下一次版本升级中。

文中所附各种源代码,在严格意义上可能存在版权问题,所以事实上这份文档带
有"半地下"性质,使用者务必自己小心卷入此类纠纷。

文中技术可能涉及未公开的、未文档化的、非规范的编程、应用接口,文档提供
的重在思想,而不保证是正确、高效、唯一的解答。

维护者不对文中任何技术引起的任何灾难性后果负任何法律上的、道义上的责任。

Ok, Let's go.

辅助说明 --

2003-10-09 12:49

辅助说明只在"外发版"中存在,稍微解释一下。

一直没有单独出一份完整的,原因很多。如果搁在1995/1996/1997时的CERNET,
这些原因都不成为原因,现在成为原因。不想多说为什么,明白的自然明白,不
明白的当我白痴好了,反正别问我。

出于"声明"中的某些理由,不能在单份完整文档中附带可能会带来麻烦的文字、
代码,比如Solaris libproc编程接口。但是,在散篇中你能找到它们。如果你
愿意,可以自己将散篇收回到该文档中,这将与我无关。一切索要残缺部分的邮
件概不回复。

本份文档的绝大多数内容在"中国教育科研网华南地区网络中心BBS"(bbs.gznet.
edu.cn)的Solaris版发布过了,包括下面处理掉的目录列表。是该版前版主CPU
师兄当年的风范促使我开始整理这份文档的,当还昔日指教之情谊。

该份文档"允许一切教育性质的自由转载、摘录、引用,无须提前知会维护者"。
我也只是义务维护一下,不对本文档拥有任何权益。如果不幸潜在拥有而践踏了
某种信念,在你看到该辅助说明的同时,我将自动放弃这种潜在可能拥有的权益。
同时意味着一切因本文档带来的麻烦,将由你个人承担。

既然来自Unix共享传统文化圈,就让它彻底回到Unix共享传统文化圈中去吧。

欢迎一切建设性的、非索要性质的Email交流。

--------------------------------------------------------------------------

目录

0. Unix/C传奇问题
0.0
0.1 Dennis Ritchie 和 Ken Thompson
0.2 W. Richard Stevens 之死
0.3 更多Unix传奇故事
0.4 那些Unix传奇人物长什么样,不会都是三头六臂吧
0.5 "3y3"是如何转换成"eye"的
0.6

1. 系统管理配置问题
1.0 如何屏蔽power-button
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 Solaris 8上tftpd的使用
1.10 为什么Sun工作站非要输入boot命令才能启动
1.11 如何让Solaris识别新增加的硬件
1.12 Solaris 9如何在命令行上增加新用户

2. 堆栈相关问题
2.0 理解SIGBUS与SIGSEGV
2.1 如何理解pstack的输出信息
2.2 Solaris的pstack实现源码
2.3 Solaris中如何获取一个C程序的调用栈回溯
2.4 如何编程获取栈底地址
2.5 如何得到一个运行中进程的内存映像
2.6 调试器如何工作的
2.7 x86/Linux上如何处理SIGFPE信号
2.8 GDB调试时没有符号表,如何设置断点

3. -lelf、-lkvm、-lkstat相关问题
3.0
3.1 如何判断可执行文件是否携带了调试信息
3.2 mprotect如何用
3.3 mmap如何用
3.4 getrusage如何用
3.5 setitimer如何用

4. 系统资源相关问题
4.0
4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况
4.2 Solaris下如何获知CPU速率
4.3 如何编程获取Solaris系统当前内存大小

5. 块设备相关问题
5.0 Solaris/FreeBSD/Linux中如何mount ISO文件
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 如何读取Solaris disk label信息
5.10 如何自己制作Solaris启动软盘
5.11 x86/Solaris如何访问FAT32分区
5.12

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.0 Solaris多线程编程与errno全局变量
8.1 Solaris内核模块中如何getcwd
8.2 Solaris下如何动态增加系统调用
8.3 如何避免一个套接字进入TIME_WAIT状态
8.4 结构在优化编译中的对齐问题
8.5 kvm编程举例: 如何编程读取shmsys:shminfo_shmmax的值
8.6 如何得到非局部变量列表
8.7 内核可加载模块引用了无法解析的符号
8.8 如何单独获得Solaris编译环境
8.9 如何获取Solaris内核可调参数列表
8.10 如何获取自Unix纪元以来的秒数,如何转换成可理解的表达方式
8.11 如何页边界对齐式分配内存
8.12 Solaris下究竟如何使用setuid/seteuid/setreuid
8.13 compile()和step()怎么用
8.14 Solaris系统中如何检查内存泄露、腐烂
8.15 How to enable microstate accounting in order to use gethrvtime(3C)
8.16 如何让普通用户可以绑定[1, 1023]闭区间上的特权端口
8.17 SPARC/Solaris 7 64-bit kernel mode下dumpadm(1M)手册页
8.18

9. 图形界面相关问题
9.1 如何避免进入Solaris的图形界面
9.2 Solaris 7的锁屏
9.3 如何调整键盘重复率
9.4 如何拔掉键盘继续运行Solaris
9.5 Solaris下如何设置显卡分辨率
9.6 Solaris下如何设置显示刷新率
9.7 在PC X Server上使用中文
9.8 如何让Solaris Console保持在字符登录界面,同时可以远程使用PC X Server

10. 网卡相关问题
10.0 怎样将第二块网卡名改成hme0
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如何对抗ARP欺骗
10.8 SPARC/Solaris 2.6/7/8下如何检查网卡混杂模式
10.9 FreeBSD下ifconfig的man手册
10.10 FreeBSD下arp的man手册
10.11 x86/Solaris如何强制设定网卡速率
10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed
10.13 x86/FreeBSD 4.3-RELEASE下LINK_ADDR(3)手册页
10.14 traceroute是怎么实现的
10.15 SPARC/Solaris 8 snoop(1M)手册页
10.16 x86/FreeBSD TCPDUMP(1)手册页
10.17 Solaris系统中ip_strict_dst_multihoming的确切含义是什么

11. package相关问题
11.0 在SPARC/Solaris 8上手工安装libpcap
11.1 Solaris下如何将二进制软件包安装到指定目标路径下
11.2 Solaris下如何自己定制二进制安装包
11.3 如何恢复/usr/bin/su的缺省安装属性
11.4 如何获知指定包与其他包之间的依赖关系
11.5 如何获得Linux命令的源代码
11.6 Solaris下如何知道某包中有哪些文件
11.7 RedHat下如何检查文件是否被改动过

12. 日志相关问题
12.0 Solaris 8如何enable FTP session log
12.1 如何查看/var/adm/utmp、/var/adm/wtmp、/var/adm/lastlog
12.2 logger/syslogd问题
12.3 如何关闭cron的日志
12.4 /var/adm/lastlog文件看上去太大了

13. 进程相关问题
13.1 如何根据进程名获得PID
13.2 如何在命令行上访问指定进程P、U两区,如何欺骗Solaris的ps
13.3 getexecname(3C)是怎么实现的
13.4 Solaris 7/8下ps输出中的问号
13.5 如何根据某种原则终止一批进程
13.6 利用libproc库编程举例
13.7 给定一个PID,如何知道它对应一个运行中的进程
13.8 Unix编程中所谓"僵尸进程"指什么
13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页
13.10 如何知道哪个进程使用了哪个端口
13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数
13.12 如何获取当前进程对应之静态映像文件的绝对路径
13.13 x86/Linux Kernel 2.4.7-10的ptrace(2)手册页
13.14 x86/Linux Kernel 2.4.7-10下如何欺骗ps

14. 一些小工具的使用
14.0
14.1 如何在命令行上进行8进制、10进制、16进制之间的转换
14.2 显示文件的三个时间戳(atime、mtime、ctime)
14.3 只在本地文件系统上查找
14.4 join命令
14.5 反汇编

15. 32-bit/64-bit相关问题
15.0
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.0 为什么用高版glibc编译生成的程序不能与低版glibc搭配运行
16.1 在Solaris 7下编写网络程序需要链接哪些库
16.2 SUID设置和LD_LIBRARY_PATH环境变量
16.3 链接过程中库的顺序
16.4 Solaris 2.x下如何构造动态链接库
16.5 如何生成linux下的共享库
16.6 /usr/lib/ld.so.1损坏或丢失
16.7 Solaris下如何使用LD_PRELOAD环境变量
16.8
16.9 Solaris 8下如何配置运行时链接环境
16.10 libcrypto.so.0.9.6是什么软件包里的
16.11 共享库的动态加载/卸载
16.12 编译时命令行指定-ldl,ldd观察时却是libdl.so.2,为什么
16.13 如何进行部分静态链接

17. 文件查看问题
17.0 如何改变vi临时目录
17.1 如何直接查看man文件
17.2 .tex文件怎么读
17.3 Solaris下怎么看.ps文件

18. 补丁相关问题
18.0
18.1 如何根据补丁号从Sun主站下载补丁
18.2 删除旧式补丁备份,释放被占用的磁盘空间
18.3 patchdiag如何使用
18.4 给Solaris 2.6安装推荐补丁集(未完成)
18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁
18.6 如何安装补丁

19. 终端相关问题
19.0 如何将stdin、stdout、stderr重定向到/dev/null
19.1 如何使Backspace键做删除操作,而不是显示^H
19.2 telnet时如何关闭本地回显
19.3 如何清空stdin的缓冲
19.4 Linux Console下一按错键就叫,怎么关
19.5 从stdin立即获取按键
19.6 如何屏蔽Ctrl-D

20. shell script问题
20.0 不用临时文件完成字符串替换
20.1 如何获取一个字符串的长度
20.2 读超时自动使用缺省值
20.3 如何删除空行、空白符组成的行
20.4 BASH中如何得到一个字符串的子串
20.5 shell script中如何关闭stdout
20.6 如何将一个文本文件开始的N行删除
20.7 以字符串(非单个字符)为分隔的析取
20.8 使用tr命令加密文件
20.9 有哪些命令用于查找定位
20.10 非递归删除目录树
20.11 如何将大写文件名转换为小写文件名
20.12 shell script中有办法实现段落注释吗
20.13 批量文件字符串替换

21. BSD相关问题
21.0 在x86/FreeBSD 4.5-RELEASE上安装nessus
21.1 如何将/var文件系统mount成mfs并支持cron daemon
21.2 如何将一个512字节的文件写入主引导扇区
21.3 x86/FreeBSD 4.3-RELEASE下FDISK(8)手册页
21.4 x86/FreeBSD 4.3-RELEASE下HEXDUMP(1)手册页
21.5 x86/FreeBSD 4.3-RELEASE下DISKLABEL(8)手册页
21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel
21.7 x86/FreeBSD下如何设置路由
21.8 x86/FreeBSD 4.4-RELEASE下DIFF(1)手册页
21.9 什么是locale
21.10 用cvsup安装vim
21.11 FreeBSD下显示、输入中文
21.12 如何在OpenSSH中限制只允许某些用户登录
21.13 在FreeBSD 4.3-RELEASE上安装libpcap、libnet
21.14 如何使自己的BMP图象成为启动logo
21.15 UDMA ICRC error是什么意思
21.16 Limiting closed port RST response什么意思
21.17 如何获取FreeBSD Kernel Source Code
21.18 /boot/defaults/loader.conf中的技巧
21.19 FreeBSD中sysctl可控内核参数
21.20 x86/FreeBSD 4.3-RELEASE下GETIFADDRS(3)手册页
21.21 FreeBSD下如何访问显存
21.22 FreeBSD下如何为指定用户设定chroot的FTP环境
21.23 如何利用FKLD动态增加一个新协议
21.24 修改/etc/mail/sendmail.cf关闭ident功能
21.25 FreeBSD下如何获取系统负载
21.26 *BSD下如何屏敝远程登录时Copyright显示
21.27 cvsup安装BASH
21.28 配置core dump
21.29 在OpenBSD 3.0上安装Gcc
21.30 在NetBSD 1.5.2上安装BASH
21.31 找不到何处启动了snmpd
21.32 FreeBSD远程root访问

22. Linux Kernel Programming
22.0
22.1 直接访问内存[显存]地址
22.2 /proc可控内核参数

23. Linux相关问题
23.0 以POST方式提交URL请求
23.1 RedHat 7.2远程root访问
23.2 TELNET/FTP连接耗时过长
23.3 Debian/Linux中如何修改本机IP
23.4 如何确认是何种Unix Release
23.5 vi/insert状态下copy/paste时不回车、只换行
23.6 如何产生core dump
23.7 Socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错

24. Unix编程相关问题
24.0 如何知道fd是有效文件句柄
24.1 如何使代码段可写
24.2 建议性文件锁与强制性文件锁
24.3 如何编写daemon程序
24.4 将编译、链接过程分开

25. AIX相关问题
25.0 如何查看AIX版本号
25.1 如何在AIX命令行上修改IP地址
25.2 如何查看RS/6000物理内存大小
25.3 AIX 4.3.3中"ls a*"不正常
25.4 AIX多线程编程与errno全局变量

--------------------------------------------------------------------------

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 <odin@gte.net>

我是他的崇拜者,用www.google.com搜索他的讣告,但这份?..刑峒八酪颍腥?/a>
知道吗?

真的仅仅是英年早逝吗?

A: Nithyanandham <m.nithyanandham@blr.spcnl.co.in>

他死于1999/09/01,家人不想让别人知道死因。讣告位于

http://www.azstarnet.com/clips/richard_stevens.html

A: joe broz <jbroz@transarc.ibm.com>

似乎是一场攀岩事故,或者滑雪事故,我不确认。

Q: W. 代表什么

A: William. My parents wanted to name me after my Uncle Bill but also
wanted to call me Richard. They figured "William Richard" sounded
better than "Richard William".

Q: 做为Guru of the Unix gurus,Stevens一生当中崇拜过什么人吗?

A: http://www.salon.com/tech/feature/2...vens/index.html

Stevens greatly admired and strove to emulate Donald Knuth, who wrote
"The Art of Computer Programming," and Brian Kernighan,
"The C Programming Language," whose books are as beautifully laid out as
they are brilliantly written.

D: knightmare@bbs.apue.net 2002-04-08 15:54

搞笑片段

我现在越来越崇拜Stevens了,因为昨天我看的电影--反斗神鹰(hot shot)--的导演
是Richard Stevens。

D: Rachel Chalmers <rachel@goop.org>

这个可与之比拟,不过这个是真的

His books are so good that they have come to symbolize intelligence. In
"Wayne's World II," Garth's girlfriend carries a copy of "Unix Network
Programming." Stevens discovered this when he took his 13-year-old son to
see the film. His son grabbed his arm and said, "Dad, that's your book!"

"I couldn't believe it," he told programmer Trent Hein. "My book was used
to define the ultimate geek, and suddenly my son thinks I'm really cool."

His son was right.

0.3 更多Unix传奇故事

A: http://www.salon.com/directory/topics/unix/index.html

0.4 那些Unix传奇人物长什么样,不会都是三头六臂吧

A: 长得并没有多帅,不过更多比他们帅的人没能在Unix历史上留下什么,我想他们
还是蛮和蔼的嘛。这里是其中九位的照片

Famous Hacker & Engineer
http://fsl.myetang.com/

0.5 "3y3"是如何转换成"eye"的

A:

下面是一个基本转换表

--------------------------------------------------------------------------
a 4 @ 4 4 @ @ 4 4 4 or @ or /-/
b |3 b 8 8 B |3 8 8 8 or |3
c C c c k C ( < < (
d |) |) D d D |) D c| |)
e 3 3 3 3 3 3 3 3 3
f |>|-| f ph F F |[ F |= |= or pH
g 6 G g 9 6 6 6 6 9
h |-| |-| H |-| H |{ H |-| |-| or #
i 1 1 i 1 ! | 1 ][ or 1 1 or | or !
j _| j j j J _| J _] J
k |< |< k |< K |< [< |< |{ or |<
l |_ 1 1 1 1 |_ |- 1 or | or [ or |_ |_ or []_
m |//| |//| M M |V| M |//| |//|
n |/| |/| n |/| N |/| N |/| |/| or ///
o 0 o o 0 0 0 0 0 0
p |> p P p P |o P |> |>
q Q q Q q Q O, Q 0 Q
r |2 r R |2 R |)/ R |2 |2
s 5 5 s 5 5 5 $ 5 or Z 5
t 7 + + 7 7 7 7 7 or + + or 7
u |_| u u u U |_| U |_| |_| or /_/
v // v V // V // V // //
w W W |//| W
x >< >< X >< X X >< X
y `/ Y y '/ Y //
| Y j or J or `/ Y
z Z z Z z 2 -/_ Z 5 Z
--------------------------------------------------------------------------

如果只进行字母到数字的转换,可以简化成

--------------------------------------------------------------------------
a -> 4
b -> 8
e -> 3
g -> 6
i -> 1
l -> 1
o -> 0
s -> 5
t -> 7
--------------------------------------------------------------------------

这里有一个转换页面,由于存在一对多的现象,转换结果可能并不完全相符

http://www.planetquake.com/turkey/l33translate.htm

比如"I'm a programmer",将被转换成"1' 4 p|209|243|2"

更多信息参看如下链接

http://www.cwru.edu/orgs/sigmataude...eunderstand.htm

D: dfbb@bbs.tsinghua.edu.cn

有个geekcode也差不多,http://www.geekcode.com/geek.html

1. 系统管理配置问题

1.0 如何屏蔽power-button

Q: 如何屏蔽Sun键盘右上角的power-button

A: Alan Coopersmith <alanc@csua.berkeley.edu> 2002年7月19日 22:12

1) 为了只允许root执行sys-suspend命令shutdown/suspend系统,可以编辑
/etc/default/sys-suspend

2) 为了禁止通过power-button激活sys-suspend命令,编辑
/usr/openwin/lib/speckeysd.map

1.1 如何给SUN工作站增加eeprom硬件口令保护

A: scz <scz@nsfocus.com>

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口令。

D: bluesfisher@smth.org

启动时Stop-N可以恢复OBP缺省设置,应该可以把这个密码去掉吧

按住Stop-N,加电,直到键盘灯闪

D: lose@smth.org 2002-03-22 01:45

试了一下Stop-N,不可以。

试了一下小四的办法是可以的,只是有几个小地方需要说一下。没有eeprom时机器是
无法启动的,所以必须要有另一块没有口令的eeprom。第一次为了热插拔方便没有将
新的eeprom插得很紧,启动之后报告IDPROM出错,不过没有关系,系统还是可以启动。
换上eeprom之后,只有console窗口可以运行,其它命令窗口无法运行命令。在
console下修改

# /usr/sbin/eeprom security-mode=none

reboot机器,一切OK。另外发现,只要你换上eeprom,都可以reboot机器而不需要口
令,重新启动之后再修改也可以,不知道这算不算一个bug。

1.2 如何增加交换空间

A: WT <wt@server.domain.top>

你无法改变分区大小,但是可以增加/删除交换文件,效果类似交换分区。下列命令
在根目录下创建一个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 <estancio@hotmail.com>

运行sys-unconfig,在此之前请man -s 1M sys-unconfig,:-)

A: chad schrock <chad@radix.net>

天啊,为了拍死一只苍蝇,你要引爆原子弹吗?

只需要做如下操作,在你的.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=<locale>
sh : LANG=<locale>
export LANG
csh : setenv LANG <locale>
bash: export LANG=en_US(zh.GBK)

3) vi /etc/default/init

增加如下内容

LANG=<locale>
LC_ALL=<locale>

重启系统。

运行"locale"命令确认改变生效。

如果你希望使用的locale并未安装,参看如下文档安装locale

Solaris 8 : <<International Language Environments Guide>>

Solaris 7 : <<Solaris Internationalization Guide For Developers>>

Solaris 2.6: <<Solaris Internationalization Guide for Developers>>

D: scz <scz@nsfocus.com> 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: quack

Linux、Solaris 2.6上的Bash试了也行。

D: scz <scz@nsfocus.com>

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 <jr@scms.rgu.ac.uk>

在这种目录下创建新文件时将采用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: 小四 <scz@nsfocus.com>

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: 小四 <scz@nsfocus.com>

修改该用户在/etc/passwd中的shell为/bin/false,在/etc/shells文件中增加
/bin/false,此时,该用户只能ftp,telnet失败。

如果/bin/false不灵,干脆换成/bin/nonexist即可。其实/bin/false不灵只是暂时
某些缓冲机制的结果,重启后必然有效,不重启的话可能要等待一定时间之后才见效
果。

如果将/bin/false换成/usr/bin/passwd,则用户可以远程telnet修改自己的口令,
也可以ftp登录,但无法远程telnet登录获取shell。

1.9 Solaris 8上tftpd的使用

A: Solaris 8上in.tftpd(1M)手册页

--------------------------------------------------------------------------
维护命令 in.tftpd(1M)

名字

in.tftpd, tftpd - Internet Trivial File Transfer Protocol Server

摘要

in.tftpd [ -s ] [ homedir ]

抽述

tftpd通常通过inetd.conf启动,缺省是注释掉的,需要手工开放。

在响应请求之前,tftpd试图切换自身的当前目录到指定的"homedir",缺省设置
是/tftpboot。

tftp不要求帐号、口令即可访问远程系统。由于缺乏身份认证信息,in.ftpd在
处理get请求时只允许访问全局可读文件。而在处理put请求时,要求server端文
件名已存在且全局可写。

in.tftpd以nobody身份运行。

选项

-s 指定该选项时,tftpd切换自身当前目录到指定"homedir"必须成功,同时
tftpd会以"homedir"为根做chroot操作。

文件

/etc/inetd.conf
--------------------------------------------------------------------------

tftpd在处理请求失败时会写/var/adm/messages,可用如下命令查看错误信息

# tail -5 /var/adm/messages

tftpd侦听69/udp口。

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 <suxm@gnuchina.org>

有三种办法

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: 老大 <willxu@public.cs.hn.cn> 2001-12-04 16:51

直接将第二块硬盘接上去,然后顺序执行如下命令,不用重新启动机器

modunload -i 0
drvconfig(1M)
devlinks(1M)
disks(1M)

如果需要重新格式化、分区、创建文件系统,就继续执行

format(1M)
newfs(1M)

1.12 Solaris 9如何在命令行上增加新用户

A:

useradd -u <uid> -g other -d /export/home/<your> -s /usr/bin/bash -c <your> -m
<your>

2. 堆栈相关问题

2.0 理解SIGBUS与SIGSEGV

Q: SIGSEGV我能理解,但有时碰上SIGBUS,这该如何理解。

A: nkwht@smth

nkwht用Google获取这样一些知识。有多种可能导致SIGBUS信号:

1) 硬件故障,不用说,程序员最常碰上的肯定不是这种情形。

2) Linux平台上执行malloc(),如果没有足够的RAM,Linux不是让malloc()失败返回,
而是向当前进程分发SIGBUS信号。

注: 对该点执怀疑态度,有机会可自行测试确认当前系统反应。

3) 某些架构上访问数据时有对齐的要求,比如只能从4字节边界上读取一个4字节的
数据类型。IA-32架构没有硬性要求对齐,尽管未对齐的访问降低执行效率。另外
一些架构,比如SPARC、m68k,要求对齐访问,否则向当前进程分发SIGBUS信号。

SIGBUS与SIGSEGV信号一样,可以正常捕获。SIGBUS的缺省行为是终止当前进程并产
生core dump。

A: Marc Rochkind <rochkind@basepath.com>

SIGBUS与SIGSEGV信号的一般区别如下:

1) SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该
指针。通常是未对齐的数据访问所致。

2) SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对
应该地址。

A: scz <scz@nsfocus.com> 2002-11-20

参"2.4 如何编程获取栈底地址"中如何捕获SIGBUS与SIGSEGV信号,并利用sigsetjmp、
siglongjmp重获控制权。

测试表明,在x86/Linux、x86/Solaris、SPARC/Solaris平台上,越过栈底的地址访
问导致SIGSEGV信号。在x86/FreeBSD、x86/NetBSD、x86/OpenBSD平台上,越过栈底
的地址访问导致SIGBUS信号,而不是SIGSEGV信号。

下面举例解释一下,什么叫未对齐的数据访问。

--------------------------------------------------------------------------
/*
* Test: SPARC/Solaris 8 64-bit kernel mode
* gcc -Wall -pipe -g -o bus bus.c
*/
#include <stdio.h>
#include <stdlib.h>

int main ( int argc, char * argv[] )
{
unsigned int i = 0x12345678;
unsigned short int *q = NULL;
unsigned char *p = ( unsigned char * )&i;

*p = 0x00;
q = ( unsigned short int * )( p + 1 );
*q = 0x0000;
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

$ ./bus
总线错误 (core dumped)
$ gdb ./bus core
GNU gdb 5.0
#0 0x1084c in main (argc=1, argv=0xffbefc54) at bus.c:16
16 *q = 0x0000;
(gdb) disas main
Dump of assembler code for function main:
0x10810 <main> : save %sp, -128, %sp
0x10814 <main+4> : st %i0, [ %fp + 0x44 ]
0x10818 <main+8> : st %i1, [ %fp + 0x48 ]
0x1081c <main+12>: sethi %hi(0x12345400), %o1
0x10820 <main+16>: or %o1, 0x278, %o0 ! 0x12345678
0x10824 <main+20>: st %o0, [ %fp + -20 ]
0x10828 <main+24>: clr [ %fp + -24 ]
0x1082c <main+28>: add %fp, -20, %o0
0x10830 <main+32>: st %o0, [ %fp + -28 ]
0x10834 <main+36>: ld [ %fp + -28 ], %o0
0x10838 <main+40>: clrb [ %o0 ]
0x1083c <main+44>: ld [ %fp + -28 ], %o0
0x10840 <main+48>: add %o0, 1, %o1
0x10844 <main+52>: st %o1, [ %fp + -24 ]
0x10848 <main+56>: ld [ %fp + -24 ], %o0
0x1084c <main+60>: clrh [ %o0 ]
0x10850 <main+64>: clr %i0
0x10854 <main+68>: b 0x1085c <main+76>
0x10858 <main+72>: nop
0x1085c <main+76>: ret
0x10860 <main+80>: restore
End of assembler dump.
(gdb) i r pc
pc 0x1084c 67660
(gdb) i r o0
o0 0xffbefbdd -4260899
(gdb) x/3bx 0xffbefbdd
0xffbefbdd: 0x34 0x56 0x78
(gdb)

从C语言来说,执行"*q = 0x0000;"时导致SIGBUS了。从汇编指令来说,执行"clrh [%o0]"
时导致SIGBUS了,寄存器%o0值为0xffbefbdd,这个地址未对齐在双字节边界上。

注意,gcc编译时并未指定-O<n>进行优化,但仍然使用clrh,而不是两次clrb。类似
的汇编指令有ldw、lduh等等。有人可能碰上读操作也导致SIGBUS,觉得不可理解,
其实读写导致SIGBUS没有本质区别,比如ldw只能读4字节边界上的地址。

bus.c是显式的未对齐。程序员实际最容易面对的是隐式未对齐,主要来自指针的强
制类型转换。下面举例说明这种情形。

--------------------------------------------------------------------------
/*
* Test: SPARC/Solaris 8 64-bit kernel mode
* gcc -Wall -pipe -g -o other_bus other_bus.c
*/
#include <stdio.h>
#include <stdlib.h>

int main ( int argc, char * argv[] )
{
unsigned int i = 0x12345678;
unsigned short int j = 0x0000;

j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

$ ./other_bus
总线错误 (core dumped)
$ gdb ./other_bus core
GNU gdb 5.0
#0 main (argc=1, argv=0xffbefc44) at other_bus.c:13
13 j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
(gdb) disas main
Dump of assembler code for function main:
0x10810 <main> : save %sp, -120, %sp
0x10814 <main+4> : st %i0, [ %fp + 0x44 ]
0x10818 <main+8> : st %i1, [ %fp + 0x48 ]
0x1081c <main+12>: sethi %hi(0x12345400), %o1
0x10820 <main+16>: or %o1, 0x278, %o0 ! 0x12345678
0x10824 <main+20>: st %o0, [ %fp + -20 ]
0x10828 <main+24>: clrh [ %fp + -22 ]
0x1082c <main+28>: lduh [ %fp + -19 ], %o0
0x10830 <main+32>: sth %o0, [ %fp + -22 ]
0x10834 <main+36>: clr %i0
0x10838 <main+40>: b 0x10840 <main+48>
0x1083c <main+44>: nop
0x10840 <main+48>: ret
0x10844 <main+52>: restore
End of assembler dump.
(gdb) i r pc
pc 0x1082c 67628
(gdb)

因此在SPARC架构上编程,一定要留神强制类型转换,务必清楚自己正在干什么,有
没有隐患。

D: yuhuan@smth.org 2004-01-30 11:48

参Linux的mmap(2)手册页

--------------------------------------------------------------------------
使用映射可能涉及到如下信号

SIGSEGV

试图对只读映射区域进行写操作

SIGBUS

试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以
前有文件内容对应,现在为另一进程截断过的内存区域。
--------------------------------------------------------------------------

2.1 如何理解pstack的输出信息

Q: 080603a7 main (1, 80479b8, 80479c0) + d53
结尾的d53是什么

A: Roger A. Faulkner <raf@sunraf.Sun.COM>

在代码段绝对地址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),以便观察到栈顶是什么函数。对于调试意想
不到的运行时错误而言,这很重要。

Q: Is it possible to unwind the stack on Solaris 8? Is there an API that I
could use? I know that with TRU64(Digital UNIX) there are the exception
handling routines: except_virtual_unwind() and except_capture_context().
Basically, what I am trying to do is print out the stack on demand,
just as dbx or gdb would.

A: Bjorn Reese <breese@mail1.stofanet.dk>

用/usr/proc/bin/pstack [-F] <pid ...>

参看这个例子代码,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 <neldredge@hmc.edu>

这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数,
参看<execinfo.h>,其他系统可能有不同的技术支持。

注意,你所使用的办法可能是唯一能够保证跨平台使用的

A: Andrew Gabriel <andrew@cucumber.demon.co.uk> Consultant Software Engineer

下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那么这
个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,好像
Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时间了。

/*
* Produce a stack trace for Solaris systems.
*
* Copyright (C) 1995-1998 Andrew Gabriel <andrew@cucumber.demon.co.uk>
* Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
*
*/

/* ......................................................................... */

#include <setjmp.h>
#include <sys/types.h>
#include <sys/reg.h>
#include <sys/frame.h>
#include <dlfcn.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>

#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" );
}

/* ......................................................................... */

Q: 我正在使用Solaris系统,"uname -a"显示如下

SunOS usunnad01 5.8 Generic_108528-14 sun4u sparc SUNW,UltraAX-i2

假设有如下代码

caller_func ()
{
called_func();
}

called_func ()
{
printf( "called_func() is being called from %s/n", some_magic_func() );
}

我期待着这样的执行输出

"called_func() is being called from caller_func()"

请问如何实现some_magic_func(),C或者汇编语言编程都可以。

D: Paul Pluzhnikov <ppluzhnikov@earthlink.net>

看看mpatrol的源代码,其中有traceback()函数可以给出整个调用栈回溯,而不仅仅
是主调函数。

D: Peter Ammon <pa44@cornell.edu>

可以考虑使用宏,这是一个例子

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -g -o test test.c
*/
#include <stdio.h>

#define CALL(x) (printf("Calling %s from %s/n", #x, __FUNCTION__), x)

int main ( void )
{
char buf[64];

while ( CALL( fgets( buf, sizeof( buff ), stdin ) ) != NULL )
{
CALL( puts( buf ) );
}
return( 0 );
}
--------------------------------------------------------------------------

A: Sun Microsystems 2000-06-13

下面演示如何编程获取当前运行中线程调用栈回溯。惟一要做的就是在应用程序中调
用csprintstack(),记得链接库选项-ldl。

--------------------------------------------------------------------------
/*
* For SPARC/Solaris 8
* gcc -D__sparc -Wall -pipe -g -o test test.c -ldl
*
* For x86/Solaris 9
* gcc -D__i386 -Wall -pipe -g -o test test.c -ldl
*/
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <setjmp.h>
#include <sys/frame.h>
#include <sys/procfs_isa.h>

#if defined(sparc) || defined(__sparc)
#define FRAME_PTR_REGISTER REG_SP
#endif

#if defined(i386) || defined(__i386)
#define FRAME_PTR_REGISTER EBP
#endif

struct frame * csgetframeptr ( void )
{
ucontext_t u;

( void )getcontext( &u );
return( ( struct frame * )( ( struct frame *
)u.uc_mcontext.gregs[FRAME_PTR_REGISTER] )->fr_savfp );
} /* end of csgetframeptr */

void cswalkstack ( struct frame *fp, int ( *operate_func ) ( void *, void * ),
void *usrarg )
{
void *savpc;

while ( fp && ( savpc = ( void * )fp->fr_savpc )
&& ( *operate_func )( savpc, usrarg ) == 0 )
{
fp = ( void * )fp->fr_savfp;
}
} /* end of cswalkstack */

static int csprintaddress ( void *pc, void *usrarg )
{
Dl_info info;
char *func;
char *lib;

if ( dladdr( pc, &info ) == 0 )
{
func = "??";
lib = "??";
}
else
{
lib = ( char * )info.dli_fname;
func = ( char * )info.dli_sname;
}
fprintf( ( FILE * )usrarg, "%s:%s+0x%x/n", lib, func,
( unsigned int )pc - ( unsigned int )info.dli_saddr );
return( 0 );
} /* end of csprintaddress */

void csprintstack ( FILE *f )
{
cswalkstack( csgetframeptr(), csprintaddress, ( void * )f );
} /* end of csprintstack */

void call_2 ( void )
{
csprintstack( stderr );
} /* end of call_2 */

void call_1 ( void )
{
call_2();
} /* end of call_1 */

void call_0 ( void )
{
call_1();
} /* end of call_0 */

int main ( void )
{
call_0();
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

[scz@ /export/home/scz/src]> gcc -D__sparc -Wall -pipe -g -o test test.c -ldl
[scz@ /export/home/scz/src]> ./test
test:call_2+0xc
test:call_1+0x4
test:call_0+0x4
test:main+0x4
test:_start+0x5c
[scz@ /export/home/scz/src]>

[scz@ /export/home/scz/src]> gcc -D__i386 -Wall -pipe -g -o test test.c -ldl
[scz@ /export/home/scz/src]> ./test
/export/home/scz/src/test:call_2+0x13
/export/home/scz/src/test:call_1+0xb
/export/home/scz/src/test:call_0+0xb
/export/home/scz/src/test:main+0x15
/export/home/scz/src/test:_start+0x5d
[scz@ /export/home/scz/src]>

2.4 如何编程获取栈底地址

Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序
获取这个栈底地址。

A: tt <warning3@nsfocus.com> 2001-06-02 19:40

假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址

x86/Linux 栈底是0xc0000000 (栈底往低地址的4个字节总是零)
SPARC/Solaris 7/8 栈底是0xffbf0000 (栈底往低地址的4个字节总是零)
SPARC/Solaris 2.6 栈底是0xf0000000 (栈底往低地址的4个字节总是零)
x86/Solaris 8 栈底是0x08048000
x86/FreeBSD 栈底是0xbfc00000 (栈底往低地址的4个字节总是零)
x86/NetBSD 1.5 栈底是0xbfbfe000
x86/OpenBSD 2.8/3.0 栈底是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 <warning3@nsfocus.com>

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o gstack gstack.c
*
* A simple example to get the current stack bottom address
* warning3 <warning3@nsfocus.com>
* 2001-06-01
*
* Modified by scz <scz@nsfocus.com>
* 2001-06-02
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>

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 0x%08x/n",
( unsigned int )get_stack_bottom() );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

D: scz <scz@nsfocus.com> 2001-06-03 00:38

W. Richard Stevens在<<Advanced Programming in the UNIX Environment>>中详细
介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。

这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV
信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。

tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD,
NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV
信号。

非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号
句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此
时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句
柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了
保存恢复,有些系统没有做,比如x86/Linux Kernel 2.4.7-10的setjmp/longjmp没
有做信号屏蔽字的保存恢复。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函
数。下面是来自SPARC/Solaris 7的setjmp(3C)

--------------------------------------------------------------------------
#include <setjmp.h>

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 <scz@nsfocus.com> 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
#

Q: 在x86/Linux平台上如何定位栈区(stack)的栈底(高址)与栈顶(低址)位置。

D: "Andrew Gabriel" <andrew@cucumber.demon.co.uk>

试试getcontext(2)

A: "Shaun Clowes" <delius@zero.spam.progsoc.org>

检查/proc/<pid>/stat,其中有两个域对应栈底(非页对齐的)与栈顶。

如果使用getcontext(2),可以通过struct ucontext的uc_mcontext成员获取栈顶位
置,参看/usr/include/sys/ucontext.h。不幸的是此时uc_stack成员未被设置,无
法简单获取栈底位置,至少对于我所检测的版本而言,Redhat 2.4.18-3smp kernel
with glibc 2.2.5。

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 <nospam@mega-nerd.com>

这是一个操作系统相关的问题。最一般的回答是使用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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>

/*
* 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: 小四 <scz@nsfocus.com> 2001-12-14 18:25

在上述代码中,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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <setjmp.h>

/*
* 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下的这此东西,实
在缺乏兴趣。

2.8 GDB调试时没有符号表,如何设置断点

Q: 在OpenBSD 3.0下想用gdb跟踪/usr/bin/skeyaudit(一个setuid-to-root程序),
却因没有符号表无法设置断点

A: tt <warning3@nsfocus.com>

# objdump -f /usr/bin/skeyaudit
start address 0x00001020
# gdb /usr/bin/skeyaudit
(gdb) x/50i 0x1020
0x1020: push %ebp <-- 这是start
0x1021: mov %esp,%ebp
0x1023: sub $0xc,%esp
0x1026: push %edi
0x1027: push %esi
0x1028: push %ebx
0x1029: lea 0x4(%ebp),%esi
0x102c: lea 0x4(%esi),%edi
0x102f: mov (%esi),%eax
0x1031: shl $0x2,%eax
0x1034: lea 0x4(%eax,%edi,1),%eax
0x1038: mov %eax,0x33f8
0x103d: mov 0x4(%esi),%ebx
0x1040: test %ebx,%ebx
0x1042: je 0x106a
0x1044: add $0xfffffff8,%esp
0x1047: push $0x2f
0x1049: push %ebx
0x104a: call 0x1774 <-- 这是一个错误处理入口
0x104f: mov %eax,0x3190
0x1054: add $0x10,%esp
0x1057: test %eax,%eax
0x1059: jne 0x1064
0x105b: mov %ebx,0x3190
0x1061: jmp 0x106a
0x1063: nop
0x1064: inc %eax
0x1065: mov %eax,0x3190
0x106a: movl $0x3000,0xfffffffc(%ebp)
0x1071: mov 0xfffffffc(%ebp),%eax
0x1074: mov 0xfffffffc(%ebp),%eax
0x1077: test %eax,%eax
0x1079: je 0x108b
0x107b: add $0xfffffff4,%esp
0x107e: push $0x3000
0x1083: call 0x1150
0x1088: add $0x10,%esp
0x108b: sub $0x10,%esp
0x108e: pushl 0x33f8 <-- 可以先在OpenBSD用gcc编译一个带符号表的程序,找
0x1094: push %edi 出这种汇编指令特征
0x1095: pushl (%esi)
0x1097: call 0x1840 <-- 这就是main
0x109c: push %eax
0x109d: call 0x307c
0x10a2: pop %ecx
0x10a3: pop %eax
0x10a4: push %ecx
---Type <return> to continue, or q <return> to quit---(Ctrl-C)Quit
(gdb) x/20i 0x1840 <-- 这里不能用disas,因为没有符号表
0x1840: push %ebp
0x1841: mov %esp,%ebp
0x1843: sub $0x13c,%esp
0x1849: push %edi
0x184a: push %esi
0x184b: push %ebx
0x184c: mov 0x8(%ebp),%ebx
0x184f: mov 0xc(%ebp),%edi
0x1852: call 0x205c
0x1857: movl $0x0,0xfffffee0(%ebp)
0x1861: movl $0x0,0xfffffedc(%ebp)
0x186b: movl $0x0,0xfffffed8(%ebp)
0x1875: movl $0x0,0xfffffed4(%ebp)
0x187f: movl $0xc,0xfffffed0(%ebp)
0x1889: call 0x314c
0x188e: test %eax,%eax
0x1890: je 0x18a4
0x1892: add $0xfffffff8,%esp
0x1895: push $0x1798
0x189a: push $0x1
(gdb) (gdb) b *0x1840
Breakpoint 1 at 0x1840
(gdb) r
Starting program: /usr/bin/skeyaudit

Breakpoint 1, 0x1840 in ?? ()
(gdb) x/20i $pc
(gdb) q
The program is running. Quit anyway (and kill it)? (y or n) y
#

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: $0 <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/hacki...F文件格式规范。

3.2 mprotect如何用

A: 小四 <scz@nsfocus.com>

# truss prtconf 2>&1 | grep sysconf
sysconfig(_CONFIG_PAGESIZE) = 8192
sysconfig(_CONFIG_PHYS_PAGES) = 16384
#

由此可知当前系统页尺寸是8192字节。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -g -static -o mtest mtest.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

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: 小四 <scz@nsfocus.com>

下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o copy_mmap copy_mmap.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* for memcpy */
#include <strings.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#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 <source> <target>/n", argv[0] );
exit( EXIT_FAILURE );
}
if ( ( src = open( argv[1], O_RDONLY ) ) < 0 )
{
perror( "open source" );
exit( EXIT_FAILURE );
}
/*
* 为了完成复制,必须包含读打开,否则mmap()失败
*/
if ( ( dst = open( argv[2], O_RDWR | O_CREAT | O_TRUNC, PERMS ) ) < 0 )
{
perror( "open target" );
exit( EXIT_FAILURE );
}
if ( fstat( src, &statbuf ) < 0 )
{
perror( "fstat source" );
exit( EXIT_FAILURE );
}
/*
* 参看前面man手册中的说明,mmap()不能用于扩展文件长度。所以这里必须事
* 先扩大目标文件长度,准备一个空架子等待复制。
*/
if ( lseek( dst, statbuf.st_size - 1, SEEK_SET ) < 0 )
{
perror( "lseek target" );
exit( EXIT_FAILURE );
}
if ( write( dst, &statbuf, 1 ) != 1 )
{
perror( "write target" );
exit( EXIT_FAILURE );
}
/*
* 读的时候指定 MAP_PRIVATE 即可
*/
sm = mmap( 0, ( size_t )statbuf.st_size, PROT_READ,
MAP_PRIVATE | MAP_NORESERVE, src, 0 );
if ( MAP_FAILED == sm )
{
perror( "mmap source" );
exit( EXIT_FAILURE );
}
/*
* 这里必须指定 MAP_SHARED 才可能真正改变静态文件
*/
dm = mmap( 0, ( size_t )statbuf.st_size, PROT_WRITE,
MAP_SHARED, dst, 0 );
if ( MAP_FAILED == dm )
{
perror( "mmap target" );
exit( EXIT_FAILURE );
}
memcpy( dm, sm, ( size_t )statbuf.st_size );
/*
* 可以不要这行代码
*
* msync( dm, ( size_t )statbuf.st_size, MS_SYNC );
*/
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

mmap()好处是处理大文件时速度明显快于标准文件I/O,无论读写,都少了一次用户
空间与内核空间之间的复制过程。操作内存还便于设计、优化算法。

文件I/O操作/proc/self/mem不存在页边界对齐的问题。至少Linux的mmap()的最后一
个形参offset并未强制要求页边界对齐,如果提供的值未对齐,系统自动向上舍入到
页边界上。

malloc()分配得到的地址不见得对齐在页边界上。

/proc/self/mem和/dev/kmem不同。root用户打开/dev/kmem就可以在用户空间访问到
内核空间的数据,包括偏移0处的数据,系统提供了这样的支持。

显然代码段经过/proc/self/mem可写映射后已经可写,无须mprotect()介入。

D: scz <scz@nsfocus.com>

Solaris 2.6下参看getpagesize(3C)手册页,关于如何获取页大小,一般是8192。
Linux下参看getpagesize(2)手册页,一般是4096。

3.4 getrusage如何用

A: 小四 <scz@nsfocus.com>

在SPARC/Solaris 2.6/7下结论一致,只支持了ru_utime和ru_stime成员,其他成员
被设置成0。修改头文件后在FreeBSD 4.3-RELEASE上测试,则不只支持ru_utime和
ru_stime成员。从FreeBSD的getrusage(2)手册页可以看到,这个函数源自4.2 BSD。

如此来说,至少对于SPARC/Solaris 2.6/7,getrusage(3C)并无多大意义。

3.5 setitimer如何用

D: scz <scz@nsfocus.com>

为什么要学习使用setitimer(2),因为alarm(3)属于被淘汰的定时器技术。

A: 小四 <scz@nsfocus.com>

下面是个x86/FreeBSD 4.3-RELEASE下的例子

--------------------------------------------------------------------------
/*
* File : timer_sample.c
* Author : Unknown (Don't ask me anything about this program)
* Complie : gcc -Wall -pipe -O3 -o timer_sample timer_sample.c
* Platform : x86/FreeBSD 4.3-RELEASE
* Date : 2001-09-18 15:18
*/

/************************************************************************
* *
* Head File *
* *
************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>

/************************************************************************
* *
* Macro *
* *
************************************************************************/

/*
* for signal handlers
*/
typedef void Sigfunc ( int );

/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/

static void Atexit ( void ( *func ) ( void ) );
static void init_signal ( void );
static void init_timer ( void );
static void on_alarm ( int signo );
static void on_terminate ( int signo );
static int Setitimer ( int which, const struct itimerval *value,
struct itimerval *ovalue );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void terminate ( void );

/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/

/************************************************************************/

static void Atexit ( void ( *func ) ( void ) )
{
if ( atexit( func ) != 0 )
{
perror( "atexit" );
exit( EXIT_FAILURE );
}
return;
} /* end of Atexit */

/*
* 初始化信号句柄
*/
static void init_signal ( void )
{
int i;

Atexit( terminate );
for ( i = 1; i < 9; i++ )
{
Signal( i, on_terminate );
}
Signal( SIGTERM, on_terminate );
Signal( SIGALRM, on_alarm );
return;
} /* end of init_signal */

static void init_timer ( void )
{
struct itimerval value;

value.it_value.tv_sec = 1;
value.it_value.tv_usec = 0;
value.it_interval = value.it_value;
Setitimer( ITIMER_REAL, &value, NULL );
return;
} /* end of init_timer */

static void on_alarm ( int signo )
{
static int count = 0;

/*
* 演示用,这很危险
*/
fprintf( stderr, "count = %u/n", count++ );
return;
}

static void on_terminate ( int signo )
{
/*
* 这次我们使用atexit()函数
*/
exit( EXIT_SUCCESS );
} /* end of on_terminate */

static int Setitimer ( int which, const struct itimerval *value,
struct itimerval *ovalue )
{
int ret;

if ( ( ret = setitimer( which, value, ovalue ) ) < 0 )
{
perror( "setitimer error" );
exit( EXIT_FAILURE );
}
return( ret );
} /* end of Setitimer */

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 terminate ( void )
{
fprintf( stderr, "/n" );
return;
} /* end of terminate */

int main ( int arg, char * argv[] )
{
init_signal();
init_timer();
while ( 1 )
{
/*
* 形成阻塞,降低CPU占用率
*/
getchar();
}
return( EXIT_SUCCESS );
} /* end of main */

/************************************************************************/

--------------------------------------------------------------------------

D: scz <scz@nsfocus.com>

讨论一个问题。getchar()的作用是降低CPU占用率,可用top命令查看。

timer_sample.c中换用ITIMER_PROF/SIGPROF后,你会发现上述程序无输出,我据此
认为getchar()形成的阻塞不计算在进程虚拟时钟中,也不认为系统正在为进程利益
而运行。

如果进一步将getchar()去掉,直接一个while()无限循环,即使换用
ITIMER_PROF/SIGPROF,程序还是有输出。不过top命令查看的结果让你吐血,CPU几
乎无空闲。

D: scz <scz@nsfocus.com>

setitimer( ITIMER_REAL, &value, NULL )导致分发SIGALRM信号,如果同时使用
alarm(),势毕造成冲突。此外注意sleep()、pause()等函数带来的冲突。

4. 系统资源相关问题

4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况

Q: Solaris下如何编程获知CPU占用率和内存占用信息呢,可移植吗?

Q: 我想写个程序遍历当前运行中的活动进程,Solaris提供相应系统调用了吗

A: Nicholas Dronen <ndronen@io.frii.com>

不可移植。man -s 4 proc,man -s 3k kstat

如果不是编程,可以用top、mpstat、vmstat、sar(1)、cpustat(1M)等等,还有
/usr/ucb/ps -aux,对于Solaris来说,后者更直接精炼,top不是标准配置。

# /usr/bin/prstat (Solaris 8 prstat(1M)手册页)
# /usr/ucb/ps -aux | head (Solaris 2.x)

Q: 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况,AIX、HP、SUN
process memory usage
process cpu time usage

A: Nate Eldredge <neldredge@hmc.edu>
man -s 3C getrusage

D: 小四 <scz@nsfocus.com>

在SPARC/Solaris 2.6/7下结论一致,只支持了ru_utime和ru_stime成员,其他成员
被设置成0。FreeBSD 4.3-RELEASE上测试,则不只支持ru_utime和ru_stime成员。从
FreeBSD的getrusage(2)手册页可以看到,这个函数源自4.2 BSD。

至少对于SPARC/Solaris 2.6/7,getrusage(3C)并无多大意义。

A: Robert Owen Thomas <robt@cymru.com>

对于Solaris,可以利用procfs接口,下面的例子获取指定进程的内存占用情况

--------------------------------------------------------------------------
/*
* @(#)memlook.c 1.0 10 Nov 1997
* Robert Owen Thomas robt@cymru.com
* memlook.c -- A process memory utilization reporting tool.
*
* gcc -Wall -pipe -O3 -o memlook memlook.c
*/
#pragma ident "@(#)memlook.c 1.0 10 Nov 1997 Robert Owen Thomas robt@cymru.com"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <sys/procfs.h>
#include <sys/param.h>
#include <unistd.h>
#include <fcntl.h>

int counter = 10;

int showUsage ( const char * );
void getInfo ( int, int );

int main ( int argc, char * argv[] )
{
int fd, pid, timeloop = 0;
char pidpath[BUFSIZ]; /* /usr/include/stdio.h: #define BUFSIZ 1024 */

switch ( argc )
{
case 2:
break;
case 3:
timeloop = atoi( argv[2] );
break;
default:
showUsage( argv[0] );
break;
} /* end of switch */
pid = atoi( argv[1] );
sprintf( pidpath, "/proc/%-d", pid ); /* -表示向左靠 */
/*
* /proc/1/是目录,但在这种用法中,就是直接打开目录,不是打开文件
*/
if ( ( fd = open( pidpath, O_RDONLY ) ) < 0 )
{
perror( pidpath );
exit( 1 );
}
if ( 0 < timeloop )
{
for ( ; ; )
{
getInfo( fd, pid );
sleep( timeloop );
}
}
getInfo( fd, pid );
close( fd );
exit( 0 );
} /* end of main */

int showUsage ( const char * progname )
{
fprintf( stderr, "%s: usage: %s < PID > [time delay]/n", progname, progname );
exit( 3 );
} /* end of showUsage */

void getInfo ( int fd, int pid )
{
prpsinfo_t prp;
prstatus_t prs;

if ( ioctl( fd, PIOCPSINFO, &prp ) < 0 )
{
perror( "ioctl" );
exit( 5 );
}
if ( ioctl( fd, PIOCSTATUS, &prs ) < 0 )
{
perror( "ioctl" );
exit( 7 );
}
if ( counter > 9 )
{
fprintf( stdout, "PID/tIMAGE/t/tRSS/t/tHEAP/t/tSTACK/n" );
counter = 0;
}
fprintf( stdout, "%u/t%-9u/t%-9u/t%-15u/t%-15u/n", pid,
( unsigned int )prp.pr_bysize, ( unsigned int )prp.pr_byrssize,
( unsigned int )prs.pr_brksize, ( unsigned int )prs.pr_stksize );
counter++;
} /* end of getInfo */
--------------------------------------------------------------------------

4.2 Solaris下如何获知CPU速率

A: Philip Brown <phil+s3@bolthole.no-bots.com>

psrinfo -v

psrinfo | grep on-line | wc -l 简单给出CPU数目

A: Philly Bob

在OK提示符下输入"module-info"获知CPU数目以及频率

A: scz <scz@nsfocus.com>

# /usr/platform/`uname -i`/sbin/prtdiag -v
# /usr/platform/`uname -m`/sbin/prtdiag -v
# /usr/bin/netstat -k cpu_info0

A: Tony Walton <tony.walton@uk.sun.com>

如果你装了Sun Workshop,还可以尝试fpversion命令

# /opt/SUNWspro/bin/fpversion
A SPARC-based CPU is available.
CPU's clock rate appears to be approximately 266.1 MHz.
Kernel says CPU's clock rate is 270.0 MHz.
Kernel says main memory's clock rate is 90.0 MHz.

Sun-4 floating-point controller version 0 found.
An UltraSPARC chip is available.
FPU's frequency appears to be approximately 277.1 MHz.

Use "-xtarget=ultra2i -xcache=16/32/1:256/64/1" code-generation option.

Hostid = 0x80BC3CB3.
#

4.3 如何编程获取Solaris系统当前内存大小

Q: 如何编程(或者有什么现成命令)获取Solaris系统当前内存大小?

A: Nithyanandham <m.nithyanandham@blr.spcnl.co.in>

几个现成命令

/usr/platform/`uname -m`/sbin/prtdiag -v | grep Memory

prtconf -v | grep Memory

如果装了GNU top,也可以直接用top命令看到。

D: scz <scz@nsfocus.com>

truss prtconf的输出中有如下内容

sysconfig(_CONFIG_PAGESIZE) = 8192
sysconfig(_CONFIG_PHYS_PAGES) = 16384
Memory size: 128 Megabytes

# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|sysconfig$"
10626] |0x0000100ec110|0x0000000001bc|FUNC |GLOB |0 |ABS |sysconfig
# find /usr/include -type f -name "*.h" | xargs grep -l _CONFIG_PAGESIZE
/usr/include/sys/sysconfig.h
# vi -R /usr/include/sys/sysconfig.h

/*
* cmd values for _sysconfig system call.
* WARNING: This is an undocumented system call,
* therefore future compatibility can not
* guaranteed.
*/

#define _CONFIG_PAGESIZE 6 /* system page size */
#define _CONFIG_PHYS_PAGES 26 /* phys mem installed in pages */

参看sysconf(3C)手册页。

_SC_PAGESIZE
_SC_PAGE_SIZE
_SC_PHYS_PAGES

A: Casper H.S. Dik <Casper.Dik@Sun.COM>

--------------------------------------------------------------------------
/*
* Program to determine the size installed physical memory on Suns.
*
* Casper Dik.
*/

#define MEGABYTE 0x00100000
#define MAXMEM 0x7ff00000
#define THEMEM "/dev/mem"

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>

int main ( int argc, char * argv[] )
{
int fd = open( THEMEM, O_RDONLY );
char c;
unsigned long pos, mapstart = 0;
int totmb = 0;

if ( fd == -1 )
{
perror( THEMEM );
exit( 1 );
}
for ( pos = 0; pos < MAXMEM; pos += MEGABYTE )
{
if (lseek( fd, pos, 0 ) == -1 )
{
perror( "lseek" );
exit( 1 );
}
if ( read( fd, &c, 1 ) == -1 )
{
int size = ( pos - mapstart ) / MEGABYTE;

if ( size != 0 )
{
printf( "found %3d MB starting at 0x%p/n", size, ( void *
)mapstart );
totmb += size;
}
mapstart = pos + MEGABYTE; /* start of next possible mapping */
}
}
printf( "Total memory size: %d MB/n", totmb );
exit( 0 );
}
--------------------------------------------------------------------------

由于需要读访问/dev/mem,普通用户用户无法使用该程序。

5. 块设备相关问题

5.0 Solaris/FreeBSD/Linux中如何mount ISO文件

A: SMTH/Unix版 2003-04-11

xfgavin@smth

在Linux中

mount -o loop your.iso <your mount point>

bcl@smth

在FreeBSD中

vnconfig vn0 your.iso
mount_cd9660 /dev/vn0c <your mount point>
... ...
umount <your mount point>
vnconfig -u vn0

Soaris@smth

在FreeBSD 5.0中

mdconfig -a -t vnode -f your.iso -u 4 (then you have /dev/md4)
mount_cd9660 /dev/md4 <your mount point>

A: Sun Microsystems

SunSolve CD-ROM Version 5.0.2 released August 2001包含一个BUG,影响了它的
安装使用。当mount上这张光盘后,没有期待中的普通文件、目录,只有一个名为
patch1或patch2的文件,这个文件实际是一个ISO映像文件。下面的操作在Solaris 8
上进行。

将SunSolve CD-ROM Version 5.0.2插入光驱,Volume Manager自动检测到光盘存在
并mount上它

$ su
# cd /cdrom/sunsolve_patch_cd_5_0_2_1
# ls

这里是进入包含ISO文件所在目录,将会看到patch1或patch2文件,如果没有,可能
光盘mount失败。可执行volcheck命令迫使Volume Manager检测光盘存在并mount它。

执行如下命令,假设/directory-path/filename确定ISO文件

# /usr/sbin/lofiadm -a /directory-path/filename

比如这里就是

# /usr/sbin/lofiadm -a /cdrom/sunsolve_patch_cd_5_0_2_1/patch1

与ISO文件相关的node number将被显示出来,比如

/dev/lofi/1

执行如下命令,指定上条命令所得到的node number做本条命令的参数

# /usr/sbin/mount -F hsfs node-number /mnt

比如这里就是

# /usr/sbin/mount -F hsfs /dev/lofi/1 /mnt

于是利用loopback file driver将ISO映像文件mount成一个块设备,现在你可以进入
/mnt目录正常使用其中的内容了。

# cd /mnt
# ls

其相应的反过程如下

$ su
# cd /
# /usr/sbin/umount /mnt

释放ISO映像文件所用node,执行如下命令,假设/directory-path/filename确定ISO
文件

# /usr/sbin/lofiadm -d /directory-path/filename

A: joerg@schily.isdn.cs.tu-berlin.de 1988-10

这是一个德国的Unix Kernel Hacker

http://www.fokus.gmd.de/research/cc...illing/private/

在这里他提供了如下驱动程序

ftp://ftp.fokus.gmd.de/pub/unix/kernel/fbk

fbk是一个伪设备驱动程序,用于在Solaris上mount那些包含文件系统的文件,比如
ISO文件(文件模拟块设备)。FreeBSD有类似的vnode driver,Linux则是loopback
driver。但是fbk最早于1988年10月就推出了。

5.1 CDROM设备究竟在哪里

Q: 为了mount光驱,需要哪些包

A: SUNWvolr SUNWcstl SUNWcstlx

D: Dennis Clarke <dclarke@blastwave.com>

1) su - root
2) /etc/init.d/volmgt stop
3) ls -1 /dev/dsk/c*s2
4) mount -F hsfs -o ro /dev/dsk/c0t6d0s2 /cdrom

或者

1) /etc/init.d/volmgt stop
2) /etc/init.d/volmgt start
3) volcheck
4) eject

观察/etc/vold.conf

Q: 如何才能知道哪个设备文件对应CDROM(c0t2d0s0?)。如果有一张光盘在CDROM里,
可以用df命令看到对应的设备文件,但是没有光盘在光驱里的时候呢?

A: /dev/sr0 是一个指向最终设备文件的符号链接,仅对SPARC有效,不包括x86

A: Logan Shaw <logan@cs.utexas.edu>

$ uname -sri
SunOS 5.8 i86pc
$ ls -l /dev/sr*
lrwxrwxrwx /dev/sr0 -> dsk/c1t0d0s2
$

我想x86下是一样的

Q: E420R,Solaris 7 11/99,我从http://sunsolve.sun.com获得一些补丁并安装了,
结果现在我的光驱出问题了。似乎mount成功了,但是找不到文件,/etc/mnttab
中没有任何有关光驱的信息,插入一张光盘会弹出一个文件管理器窗口,但是没
有文件。

A: Danny Mann <dma@wwa.com>

检查是否打了如下Solaris 7内核补丁106541-13和 -14。这两个补丁有问题。解
决办法是禁止vold,手工mount光驱。

A: rschicht@my-deja.com <rschicht@my-deja.com>

试试volrmmount -d命令。用patchadd -p检查是否安装了补丁106541-14,访问如
下链接

http://sunsolve.Sun.COM/pub-cgi/sho...es/patch-access

获取补丁106541-14的说明,阅读NOTE 15。

A: 补丁106541-14的说明,NOTE 15

1. 首先禁止掉vold守护进程
# /etc/init.d/volmgt stop

2. 手工mount光驱(设备文件名可能不同)
# /etc/mount -F hsfs -o ro /dev/dsk/c0t2d0s0 /cdrom

查看/etc/vfstab、/dev/dsk确认光驱所在设备文件名。

5.2 如何弹出光驱

Q: 在安装Oracle 8i时,系统提示插入第二张光盘,但是此时无法成功eject第一张
光盘,终端挂起,杀掉Oracle 8i的安装进程也无济于事。唯一的办法是reset。

A: Sergey Kurganov <mmerfi@home.com>

下面的操作或许有所帮助

1) 终止卷管理器
# /etc/init.d/volmgt stop

2) unmount光驱,手动eject

3) 重启卷管理器
# /etc/init.d/volmgt start

D: plane@smth.org 2002-02-26 01:03

装Oracle 9的时候,安装文档特意提醒要用绝对路径才能换盘。

5.3 如何利用超级块进行恢复工作

Q: Sun工作站在reboot时掉电了,用安装光盘启动进入单用户模式,执行fsck命令时
报错

Stop-A
ok boot cdrom -s

INIT: SINGLE USER MODE
# fsck -o b=32 /dev/rdsk/c0t5d0s*
Alternate super block location: 32.
** /dev/rdsk/c0t5d0s0
BAD SUPER BLOCK: MAGIC NUMBER WRONG
USE AN ALTERNATE SUPER-BLOCK TO SUPPLY NEEDED INFORMATION;
eg. fsck [-F ufs] -o b=# [special ...]
where # is the alternate super block. SEE fsck_ufs(1M).
Alternate super block location: 32.
** /dev/rdsk/c0t5d0s1
BAD SUPER BLOCK: MAGIC NUMBER WRONG
USE AN ALTERNATE SUPER-BLOCK TO SUPPLY NEEDED INFORMATION;
eg. fsck [-F ufs] -o b=# [special ...]
where # is the alternate super block. SEE fsck_ufs(1M).
Alternate super block location: 32.
** /dev/rdsk/c0t5d0s2
BAD SUPER BLOCK: MAGIC NUMBER WRONG
USE AN ALTERNATE SUPER-BLOCK TO SUPPLY NEEDED INFORMATION;
eg. fsck [-F ufs] -o b=# [special ...]
where # is the alternate super block. SEE fsck_ufs(1M).
Alternate super block location: 32.

A: Sree Mokkapati <sree@broadcom.com>

正确的用法就在错误提示信息里,你应该使用另外的超级块进行恢复工作,32仅
仅是常用备份超级块之一。

fsck -F ufs -o b=32 device_name

此外如果想知道还有哪些备份超级块可用,执行

newfs -Nv device_name
(等价于 mkfs -F ufs -oN -m /dev/rdsk/cXtXdXsX )

先用df等命令确认原始device_name。

D: scz <scz@nsfocus.com> 2001-10-12 17:01 修订

SPARC/Solaris的硬盘损坏多半是文件系统根区被破坏,并不需要拆卸硬盘到其他
机器上mount后fsck,找一张Solaris安装光盘

Stop-A进入OBP状态,在ok提示符下输入

ok> boot cdrom -s

进入单用户模式。此时原有根文件系统并未mount上来,也不需要mount原有根文
件系统,直接

newfs -Nv /dev/rdsk/c0t0d0s0

找出原根文件系统所有备份超级块号

fsck -y -F ufs -o b=<任一备份超级块号> /dev/rdsk/c0t0d0s0

这里假设原根文件系统的原始设备名是/dev/rdsk/c0t0d0s0。其他文件系统的原
始设备名可以在系统完好时 df -k 获取,或者从/etc/vfstab中获取信息。比如

/dev/rdsk/c0t0d0s0 /
/dev/rdsk/c0t0d0s1 /var
/dev/rdsk/c0t0d0s6 /usr
/dev/rdsk/c0t0d0s7 /export/home

根文件系统一定是/dev/rdsk/c0t0d0s0,可以先用fsck处理根文件系统,然后

mount -F ufs /dev/dsk/c0t0d0s0 /mnt
more /mnt/etc/vfstab

这样就能得到其它文件系统的原始设备名了。此外,fsck使用的是/dev/rdsk/...
而mount使用的是/dev/dsk/...,注意这个区别。

vfstab(4)解释得很模糊,回头我上www.google.com去找找其他资料。

The fsck pass value of 2 means that the file system will be checked,
but not sequentially

Q: /etc/vfstab轻微损坏,启动时只能输入root口令进入单用户模式,根文件系统被
只读mount。不想动用Solaris安装光盘,怎么办。

A: mount -F ufs -o remount,rw /dev/dsk/c0t0d0s0 /

5.4 Solaris root口令忘记了

Q: 忘记了root口令,怎么办

A: Steve Menard <opsmaster@yahoo.com>

启动时按Stop-A进入ok提示符
ok boot cdrom -s (放入启动安装光盘)
mount -F ufs /dev/dsk/c0t0d0s0 /mnt (这里指定原根区对应的设备名)
TERM=vt100;export TERM (这一步必须做,否则vi失败)
vi /mnt/etc/shadow
删除root口令加密串,比如
root:WxzL460hohWsU:10724::::::
删除WxzL460hohWsU,确认你还有8个冒号,重启动

或者 /usr/sbin/reboot -- "cdrom -s"

A: Philip Brown <phil+s3@bolthole.no-bots.com>

使用vi有很多麻烦的地方,可以考虑sed
mount -F ufs /dev/dsk/c0t0d0s0 /mnt
sed 's/:WxzL460hohWsU:/::/' /mnt/etc/shadow > s
mv s /mnt/etc/shadow

或者使用ed
mount -F ufs /dev/dsk/c0t0d0s0 /mnt
ed /mnt/etc/shadow
1s/root:[^:]*:/root::/ (注意,前面是数字1,不是字母l)
w
q

5.5 如何使用fmthard

A: Seán Boran <sean@boran.com>

如果希望对第二块物理硬盘的分区与第一块物理硬盘一样,考虑fmthard和prtvtoc的
结合使用,要比手工format快得多。比如,第一块物理硬盘是target 3,第二块物理
硬盘是target 1,我们希望第二块物理硬盘磁盘卷标是"mirror",做如下操作:

/usr/sbin/prtvtoc /dev/rdsk/c0t3d0s2 | /usr/sbin/fmthard -n mirror -s -
/dev/rdsk/c0t1d0s2

man -s 1M fmthard了解更多细节。

5.6 如何从光盘恢复Solaris 7的引导扇区

A: paranoid@bbs.tsinghua.edu.cn

在安装盘里有一个tools目录,进去后有一个命令叫做installboot

A: melonm@bbs.tsinghua.edu.cn

比如
installboot /usr/platform/`uname -i`/lib/fs/ufs/bootblk /dev/rdsk/c0t1d0s0

5.7 Solaris支持类似微软autorun.inf文件的功能吗

Q: 我自己制作了一张光盘,同时用于Solaris和Windows。在Windows环境下,可以利
用autorun功能,当插入光盘的时候自动调用喜爱的浏览器打开一个文件。不知道
Solaris 7/8下是否存在类似功能。

A: hakteng

是的,从Solaris 8(CDE version 1.4)开始支持类似功能了

o 创建一个名为"volstart"的脚本文件,比如

--------------------------------------------------------------------------
#! /bin/ksh
#
# This is a CD volume start script. This start script is designed
# to be automatically run when the CD is inserted into a Solaris
# system's CDrom drive.
#
# Note: not all Solaris systems have an auto volstart ability. If this
# CD is inserted into a CDrom drive of a Solaris system without the
# volstart ability, volstart can also be run manually by executing it
# from either the desktop's file manager or from a Unix command line.
full_name=$0
dir_name=`/usr/bin/dirname $full_name`
if [[ -x /usr/dt/bin/dtaction ]]; then
# Run the CDrom's installer program
/usr/dt/bin/dtaction Run $dir_name/installer
fi
--------------------------------------------------------------------------

o 将"volstart"文件放在光盘根目录下

o /usr/dt/bin/sdtvolcheck脚本中存在如下语句
if [[ -x $mountPt/volstart ]];then exec $mountPt/volstart;
于是,当插入光盘的时候volstart脚本被执行,对于上例,最终导致installer被
执行

5.8 如何修改/dev/null的属性

Q: /devices/pseudo/mm@0:null的属性是0620 root tty,我想
chmod 666 /devices/pseudo/mm@0:null ,但是几分钟后,属性被修改回
0620 root tty,怎么办

A: Markus Mayer <mmayer@iname.com>

查看/etc/minor_perm文件,

# grep -s null /etc/minor_perm
mm:null 0620 root tty

修改该文件中的这一行成"mm:null 0666 root sys"即可。

5.9 如何读取Solaris disk label信息

5.10 如何自己制作Solaris启动软盘

Q: 我知道可以去
http://soldc.sun.com/support/drivers/dca_diskettes/
下载启动软盘的映象文件,可我还想知道它最初是如何制作出来的

A: 小四 <scz@nsfocus.com>

1) 用fdformt格式化软盘

2) 用newfs在软盘上创建新的文件系统

3) 将软盘mount上来

4) 用cp命令复制the second-level disk booter(boot或者ufsboot)到软盘,比如
/platform/sun4u/ufsboot。参看installboot(1M)、boot(1M)手册页

5) 用installboot命令安装boot block到软盘,比如
installboot /usr/platform/`uname -i`/lib/fs/ufs/bootblk /dev/rdsk/c1t0d0s0

6) 用cp命令复制必要的工具文件到软盘

7) unmount软盘

8) 用eject命令弹出软盘

5.11 x86/Solaris如何访问FAT32分区

A: Dan Anderson <dan@drydog.com>

mount -F pcfs /dev/dsk/c0t0d0p0:1 /mnt/<...> # SCSI
mount -F pcfs /dev/dsk/c0d0p0:1 /mnt/<...> # ATAPI

c0 控制器ID
t0 SCSI ID (对于ATAPI省略)
d0 对于SCSI总是0,对于ATAPI是硬盘号
p0 p0对应第一个主分区表项
:1 对应逻辑驱动器(c - z 或 1 - 24)

有些报告说如果FAT32分区不对应第一个主分区表项,mount失败,感觉x86/Solaris
对pcfs支持混乱。

A: spp(低音炮)

在SPARC/Solaris 7上df -k

# df -k
/dev/dsk/c0t0d0s0 /
/dev/dsk/c0t0d0s6 /usr
/dev/dsk/c0t0d0s7 /export/home

在x86/Solaris 8上df -k

# df -k
/dev/dsk/c0d0s0 /
/dev/dsk/c0d0s7 /export/home

c 硬盘控制器的位置,比如主板第二个IDE接口上的第一个硬盘(主盘)对应c1d0
t 只SPARC有,SCSI ID
d 某一确定硬盘控制器(c参数决定)上硬盘位置
p 只x86有,对应MS系统的Partition概念
s slice号,Solaris系统的概念,不太好解释,如果和p一起出现,可以理解成类似
MS逻辑驱动器的概念

假设x86架构上某硬盘在主引导扇区有两个主分区表项,第一个为FAT32分区,第二个
为Solaris分区,Solaris分区上划分了两个slice,一个为根文件系统/、一个为swap
区,则分别表示为/dev/dsk/c1d0p0:1(FAT32)、/dev/dsk/c1d0p1s0(/)、
/dev/dsk/c1d0p1s1(swap)

在mount FAT32分区时应该用

mount -F pcfs /dev/dsk/c1d0p0:1 /mnt/<mount_point>

D: 小四 <scz@nsfocus.com>

注意,Solaris的slice概念和FreeBSD的slice概念不同,FreeBSD的slice概念就是MS
的partition概念,而Solaris的slice概念类似于MS扩展分区上的逻辑驱动器概念。

6. /etc/system可调资源限制

6.1 Solaris下如何限制每个用户可拥有的最大进程数

A: Casper Dik

在/etc/system设置
set maxuprc = <num>

Q: maxusers参数究竟影响了什么

A: Casper Dik

下面以/etc/system语法格式举例说明:

*
set maxusers = <以MB为单位计的可用物理内存数量>

* 系统所允许的最大进程数,通常最多30000
set max_nprocs = 10 + 16 * maxusers

* 每个用户可以拥有的最大进程数(为超级用户保留5个)
set maxuprc = max_nprocs - 5;

# sysdef | sed -n '/System Configuration/,$p'

6.2 如何配置系统使之支持更多的伪终端

A: Argoth

不要试图通过'/usr/bin/adb -k'到达目的。

a. 如果Solaris版本小于7,修改/etc/system,增加如下行

set pt_cnt=<num>

执行/usr/sbin/reboot -- -r,或者Stop-A,执行boot -r

b. 对于Solaris 8,支持的伪终端数目根据需要动态改变,系统依然有一个内部限制,
但是这个值非常大。如果"pt_cnt"变量小于这个内部限制,将被忽略。一般情况
下,不再需要指定"pt_cnt"变量。但还是有某些罕见的情形,需要设置"pt_cnt"
变量大于内部限制。

6.3 如何增加每个进程可打开文件句柄数

A: Casper H.S. Dik <Casper.Dik@Sun.COM>

从Solaris 2.4开始,可以通过修改/etc/system实现

* set hard limit on file descriptors
set rlim_fd_max = 4096
* set soft limit on file descriptors
set rlim_fd_cur = 1024

软限制超过256时,某些应用程序会出问题,尤其BCP程序。软限制超过1024时,那些
使用select()的应用程序可能会出问题。Solaris 7之前,select()使用的文件句柄
数不能超过1024。Solaris 2.6的RPC代码被重写过了,使用poll()代替select(),可
以使用超过1024的文件句柄。Solaris 2.6之前,如果软限制超过1024,所有RPC服务
很可能崩溃。

Solaris 7下select()可以使用最多达65536的文件句柄,64-bit应用程序缺省情况如
此。如果是32-bit应用程序,需要指定给FD_SETSIZE一个更大的值,重新编译。

如果程序使用标准输入/输出(stdio),或者调用那些使用stdio的库函数,当打开的
文件超过256时,程序可能会出问题,这个限制是stdio的限制。当程序需要大量文件
句柄时,应该想办法保留一些小数字的文件句柄,让stdio使用它们。

Solaris 7下64-bit应用程序不再受这个stdio限制的影响。如果你的确需要超过256
个FILE *,而又不能使用Solaris 7,或者需要运行32-bit代码,考虑使用来自AT&T
的SFIO(http://www.research.att.com/sw/tools/sfio/。

A: qaz@smth.org

检查当前设置

# ulimit -H -n
1024
# ulimit -S -n
64
#

对于Solaris,建议修改/etc/system后重启

* set hard limit on file descriptors
set rlim_fd_max=0x8000
* set soft limit on file descriptors
set rlim_fd_cur=0x8000

然后 ulimit -S -n 8192

对于Linux

echo 65536 > /proc/sys/fs/file-max

然后 ulimit -S -n 8192

对于FreeBSD

编辑/etc/sysctl.conf文件(或者sysctl -w,参看SYSCTL.CONF(5))
kern.maxfiles=65536
kern.maxfilesperproc=32768

Q: Linux下如何加大系统可以打开的文件数

A: planck.bbs@bbs.nju.edu.cn

echo <num> > /proc/sys/fs/file-max

6.5 做了setuid()这类调用的程序如何产生core dump

Q: 做了setuid()这类调用的程序不会产生core文件,可我需要调试这个程序。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o suidtest suidtest.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main ( int argc, char * argv[] )
{
int *ptr = NULL;

printf( "Current uid = %d euid = %d/n", ( int )getuid(), ( int )geteuid() );
printf( "Result of seteuid( 500 ) = %d/n", seteuid( 500 ) );
printf( "Current uid = %d euid = %d/n", ( int )getuid(), ( int )geteuid() );
creat( "/tmp/scz_blah", S_IRWXU );
printf( "Result of setuid( 0 ) = %d/n", setuid( 0 ) );
printf( "Current uid = %d euid = %d/n", ( int )getuid(), ( int )geteuid() );
*ptr = 0;
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

# gcc -Wall -pipe -O3 -o suidtest suidtest.c
# strip suidtest
# file suidtest
suidtest: ELF 32-位 MSB 可执行 SPARC 版本 1,动态链接,除去
# ls -l suidtest
-rwxr-xr-x 1 root other 4988 6月 29 21:21 suidtest*
# ./suidtest
Current uid = 0 euid = 0
Result of seteuid( 500 ) = 0
Current uid = 0 euid = 500
Result of setuid( 0 ) = 0
Current uid = 0 euid = 0
段错误
# ls -l core
core: 无此文件或目录
#

这个程序应该core dump,但是现在没有core文件产生。注意,此时suidtest仅仅是
自己调用了setuid(),并非被"chmod u+s suidtest"过。有无/etc/system内核可配
置参数改变这种行为。

A: Sun Microsystems 2001-04-11

出于安全考虑,suid程序以及调用setuid()的程序缺省情况下不产生core dump。如
果确实需要产生core dump以便进行调试,修改/etc/system文件并重启系统

* 缺省该值为0,此时禁止suid程序以及调用setuid()的程序core dump
set allow_setid_core = 1

对于Solaris 2.6,需要先打补丁105181-22或更高版本,才能使用上述技术。对于
7及其更高版本的Solaris操作系统,不需要任何补丁。

A: 小四 <scz@nsfocus.com> 2001-07-30 20:14

对于SPARC/Solaris 7,修改/etc/system

set allow_setid_core = 1

结果在boot的时候看到提示信息说这个内核参数已经废弃(obsolte),推荐使用
coreadm(1M)。

对于SPARC/Solaris 7来说,为了方便调试,执行coreadm -e proc-setid命令即可。

6.6 消息队列调整

Q: 在/etc/system中如何调整消息队列

A: <solaris@sean.de>

消息队列统一使用 msgsys:msginfo_ 前缀。你可以用sysdef获取一些缺省值,还可
以参看/usr/include/sys/msg.h头文件了解更多信息。此外不要忘记<<APUE>>

msgsys:msginfo_msgmap

default 100 max 2147483647 <sys/msg.h> 100

msgsys:msginfo_msgmax

default 2048 max 2147483647 <sys/msg.h> 8192 typical value 2048

msgsys:msginfo_msgmnb

default 4096 max 2147483647 <sys/msg.h> 2048 typical value 4096

msgsys:msginfo_msgmni

default 50 max 2147483647 <sys/msg.h> 50 typical value 50

msgsys:msginfo_msgssz

default 8 max 2147483647 <sys/msg.h> 8

msgsys:msginfo_msgtql

default 40 max 2147483647 <sys/msg.h> 50 typical value 40

msgsys:msginfo_msgseg

default 1024 max 32767 <sys/msg.h> 1024

7. DNS相关问题

7.1 如何进行DNS区传输

A: scz <scz@nsfocus.com>

用nslookup是最普遍适用的
nslookup
> server ns.tsinghua.edu.cn
> set type=axfr
> ls -d tsinghua.edu.cn [> tsinghua.txt] (方括号里的可选)

有些系统提供了dig命令
dig @ns.tsinghua.edu.cn axfr tsinghua.edu.cn

A: lgwu

有些系统提供了host命令,这个命令不太保险
host -l net.tsinghua.edu.cn (后面指定域)
host -l ncic.ac.cn

7.2 如何获知权威名字服务器

A: scz <scz@nsfocus.com>

nslookup
> set query=ns
> ncic.ac.cn (获知管辖该域的权威名字服务器)
Authoritative answers can be found from:
gatekeeper.ncic.ac.cn internet address = 159.226.41.188
> server gatekeeper.ncic.ac.cn
> set type=axfr (准备区传输)
> ls -d ncic.ac.cn > ncic.txt

7.3 如何配置DNS的委托解析

Q: 我想把子域DNS解析下放到下面去,在我这里如何配置

A: zhangql@bbs.tsinghua.edu.cn

子域 IN NS <负责子域DNS解析的IP>

7.4 如何获知BIND的版本号

Q: 如何识别当前运行的bind是什么版本

A: M. Zuber <helmlein@hotmail.com>

dig @<victim_ip> txt chaos VERSION.BIND

或者

nslookup
server <victim_ip>
set query=txt
set class=chaos
VERSION.BIND

但是这个返回结果可以通过/etc/named.conf自己设置,并不可靠。如果你正在运
行BIND 8,可以执行

/usr/sbin/ndc status

D: scz <scz@nsfocus.com> 2002-11-27 16:30

BIND支持如下查询请求,参看src/bin/named/ns_req.c中的req_query()函数

dig @<victim_ip> txt chaos VERSION.BIND

dig @<victim_ip> txt chaos HOSTNAME.BIND

A: backend <backend@nsfocus.com>

#! /bin/sh
# bv (Bind Version) script
# written by backend@nsfocus.com

USAGE="Usage: $0 <address>"

if [ $# -ne 1 ] ; then
echo $USAGE
exit
fi

if [ ! -f /usr/bin/dig ]; then
echo -en "//033[1;31mCan't find /"dig/" program.//033[0;39m/n/n"
exit
fi

VER=`/usr/bin/dig @$1 VERSION.BIND chaos txt | grep "VERSION.BIND"`

if [ "x$VER" = "x" ]; then
echo -en "//033[1;31mSorry. Can't get BIND version.//033[0;39m/n/n"
else
echo -en "BIND version of //033[1;33m$1//033[0;39m = "
echo -en "//033[1;33m"
echo $VER | awk '{print $5;}'

echo -en "//033[0;39m/n"
fi

A: deepin <deepin@nsfocus.com>

很多主机没有dig,最方便的办法是
nslookup -q=txt -class=chaos VERSION.BIND IP-addr
如果要美观一点,所以可以用这样的一个小脚本

#! /bin/sh
if [ $# = 0 ];then echo "useage: $0 IP-Addr."; exit 1;fi
VER=`nslookup -q=txt -class=chaos VERSION.BIND $1 | grep "VERSION.BIND"`
if [ $? = 0 ] ; then
echo -en "BIND version of //033[1;33m$1//033[0;39m = " `echo $VER | awk
'{print $4,$5,$6;}'` "//033[0;39m/n"
else
echo -en "//033[1;31mSorry. Can't get BIND version.//033[0;39m/n/n"
fi

命令行上直接指定IP,会进行反向域名解析,有可能失败,进入nslookup之后server
指定IP,则无此问题。

Q: Solaris上怎么查看BIND版本号

A: cc@水木清华 2002-04-03 10:33

/usr/sbin/in.named -d 1

然后Ctrl-C退出(如果不存在/etc/named.conf,它会自动退出)。在当前目录下多了
一个文件named.run,其中前几行就有你要的版本号(grep Version named.run)

可以先检查日志确定,grep BIND /var/adm/messages

7.5 Solaris/FreeBSD/Linux如何指定域名解析的顺序

Q: 如何在Solaris中使/etc/resolv.conf的设置生效

A: cp /etc/nsswitch.dns /etc/nsswitch.conf
或者
vi /etc/nsswitch.conf

hosts: files dns

Q: FreeBSD中有类似Solaris的/etc/nsswitch.conf的文件吗

A: /etc/host.conf

--------------------------------------------------------------------------
# First try the /etc/hosts file
hosts
# Now try the nameserver next.
# 如果不希望做反向域名解析,则注释掉下面这行
# bind
# If you have YP/NIS configured, uncomment the next line
# nis
--------------------------------------------------------------------------

Q: Linux中有类似Solaris的/etc/nsswitch.conf的文件吗

D: /etc/host.conf

--------------------------------------------------------------------------
order hosts, bind, nis
multi on
--------------------------------------------------------------------------

D: rai@SMTH Unix 2001-11-28 09:42

改了/etc/host.conf还是不行,后来试了一下/etc/nsswitch.conf就可以了,Linux
也有这个文件的,必须保证下一行中有dns

--------------------------------------------------------------------------
# hosts: db files nisplus nis dns
hosts: files nisplus nis dns
--------------------------------------------------------------------------

作者:飞猫头衔:版主发布时间:  2004-12-23 17:58:43

第1楼: 
8. Solaris编程相关问题

8.0 Solaris多线程编程与errno全局变量

Q: 我正在进行Solaris多线程编程,如果errno是全局变量,则任意线程都可能修改
其值,而我需要判断errno,此时应该注意什么问题。

A: Casper H.S. Dik <Casper.Dik@Sun.COM> 2002-08-05 21:47

使用gcc -D_REENTRANT,此时errno是每个线程相关的,不再是全局变量。当正确地
包含<errno.h>之后,errno被重新定义过:

extern int *___errno();
#define errno (*(___errno()))

这个函数返回一个指针,指向一个线程相关整数。注意,你仍然可以使用&errno。

8.1 Solaris内核模块中如何getcwd

Q: 在Solaris 7 64-bit内核模块中如何获知一个进程的当前工作目录(cwd),getcwd
并不是一个系统调用

A: Rich Teer <rich@rite-group.com>

最好通过u->u_cdir获取当前工作目录(cwd)的vnode(v节点)。但这依赖于内核当前上
下文,curproc可能并不对应你期望的进程。

/usr/include/sys/user.h

typedef struct user
{
... ...
/*
* protected by p_lock
*/
struct vnode * u_cdir; /* current directory */
struct vnode * u_rdir; /* root directory */

8.2 Solaris下如何动态增加系统调用

8.3 如何避免一个套接字进入TIME_WAIT状态

Q: 我正在写一个unix server程序,不是daemon,经常需要在命令行上重启它,绝大
多数时候工作正常,但是某些时候会报告"bind: address in use",于是重启失
败。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。至于
TIME_WAIT状态,你无法避免,那是TCP协议的一部分。

Q: 如何避免等待60秒之后才能重启服务

A: Erik Max Francis <max@alcyone.com>

使用setsockopt,比如

--------------------------------------------------------------------------
int option = 1;

if ( setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option,
sizeof( option ) ) < 0 )
{
die( "setsockopt" );
}
--------------------------------------------------------------------------

Q: 编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?

A: 这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用
端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,
指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧
使用同一端口,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期
望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不
可能。

一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端
口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组
还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使
用 SO_REUSEADDR 选项。

Q: 在客户机/服务器编程中(TCP/SOCK_STREAM),如何理解TCP自动机 TIME_WAIT 状
态?

A: W. Richard Stevens <1999年逝世,享年49岁>

下面我来解释一下 TIME_WAIT 状态,这些在<<Unix Network Programming Vol I>>
中2.6节解释很清楚了。

MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现
都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。

TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟。

IP头部有一个TTL,最大值255。尽管TTL的单位不是秒(根本和时间无关),我们仍需
假设,TTL为255的TCP报文在Internet上生存时间不能超过MSL。

TCP报文在传送过程中可能因为路由故障被迫缓冲延迟、选择非最优路径等等,结果
发送方TCP机制开始超时重传。前一个TCP报文可以称为"漫游TCP重复报文",后一个
TCP报文可以称为"超时重传TCP重复报文",作为面向连接的可靠协议,TCP实现必须
正确处理这种重复报文,因为二者可能最终都到达。

一个通常的TCP连接终止可以用图描述如下:

client server
FIN M
close -----------------> (被动关闭)
ACK M+1
<-----------------
FIN N
<----------------- close
ACK N+1
----------------->

为什么需要 TIME_WAIT 状态?

假设最终的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发
最终的ACK,否则会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连
接的两个方向(全双工关闭),client必须进入 TIME_WAIT 状态,因为client可能面
临重发最终ACK的情形。

{
scz 2001-08-31 13:28

先调用close()的一方会进入TIME_WAIT状态
}

此外,考虑一种情况,TCP实现可能面临先后两个同样的相关五元组。如果前一个连
接处在 TIME_WAIT 状态,而允许另一个拥有相同相关五元组的连接出现,可能处理
TCP报文时,两个连接互相干扰。使用 SO_REUSEADDR 选项就需要考虑这种情况。

为什么 TIME_WAIT 状态需要保持 2MSL 这么长的时间?

如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。
第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二
个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT
状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被
丢弃。建立第二个连接的时候,不会混淆。

A: 小四 <scz@nsfocus.com>

在Solaris 7下有内核参数对应 TIME_WAIT 状态保持时间

# ndd -get /dev/tcp tcp_time_wait_interval
240000
# ndd -set /dev/tcp tcp_time_wait_interval 1000

缺省设置是240000ms,也就是4分钟。如果用ndd修改这个值,最小只能设置到1000ms,
也就是1秒。显然内核做了限制,需要Kernel Hacking。

# echo "tcp_param_arr/W 0t0" | adb -kw /dev/ksyms /dev/mem
physmem 3b72
tcp_param_arr: 0x3e8 = 0x0
# ndd -set /dev/tcp tcp_time_wait_interval 0

我不知道这样做有什么灾难性后果,参看<<Unix编程/应用问答中文版>>的声明。

Q: TIME_WAIT 状态保持时间为0会有什么灾难性后果?在普遍的现实应用中,好象也
就是服务器不稳定点,不见得有什么灾难性后果吧?

D: rain@bbs.whnet.edu.cn

Linux 内核源码 /usr/src/linux/include/net/tcp.h 中

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to successfully
* close the socket, about 60 seconds */

最好不要改为0,改成1。端口分配是从上一次分配的端口号+1开始分配的,所以一般
不会有什么问题。端口分配算法在tcp_ipv4.c中tcp_v4_get_port中。

A: 小四 <scz@nsfocus.com> 2002-07-07 15:20

写三个脚本自动清除所有TIME_WAIT状态TCP连接:

SPARC/Solaris 8 64-bit kernel mode

--------------------------------------------------------------------------
#! /sbin/sh

ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | /
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcpb_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcpb_addr+0x30/Z 0t6
$tcpb_addr+0x40/W -6
/$q
NSFOCUS_EOF
done
--------------------------------------------------------------------------

SPARC/Solaris 7 64-bit kernel mode

--------------------------------------------------------------------------
#! /sbin/sh

ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | /
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcp_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcp_addr+0x50/Z 0t6
$tcp_addr+0x58/W -6
/$q
NSFOCUS_EOF
done
--------------------------------------------------------------------------

SPARC/Solaris 2.6 32-bit kernel mode

--------------------------------------------------------------------------
#! /sbin/sh

/usr/sbin/ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " "
$18}' | /
egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcp_addr
do
adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF
$tcp_addr+0x28/W 0t6
$tcp_addr+0x2c/W -6
/$q
NSFOCUS_EOF
done
--------------------------------------------------------------------------

8.4 结构在优化编译中的对齐问题

Q: 什么是结构在优化编译中的对齐问题

A: 小四 <scz@nsfocus.com>

看这样两个结构定义

struct xxx
{
unsigned char a;
unsigned int b;
};

struct yyy
{
unsigned char a;
unsigned int b;
} __attribute__ ((packed));

或者

#pragma pack(1)
struct yyy
{
unsigned char a;
unsigned int b;
};
#pragma pack()

假设是32-bit编译,则xxx占用8字节,而yyy占用5字节。xxx进行了所谓的结构成员
优化对齐。我们可以定义一个宏来获取xxx和yyy中b成员的偏移量,你会发现这个偏
移对于xxx是4,对于yyy则是1。

#define OFFSETOF(TYPE, MEMBER) ((size_t)&((TYPE)0)->MEMBER)

OFFSETOF( struct xxx *, b ) -> 4
OFFSETOF( struct yyy *, b ) -> 1

Q: 我正在写一个流模块,其中用到了#pragma pack(),当使用

gcc -D_KERNEL -c abc.c
ld -r -o abc abc.o

编译链接时,一切正常。为了获得64-bit模块,我必须使用Sun Workshop 5.0,
结果导致系统崩溃。访问

http://docs.sun.com/htmlcoll/coll.3...gmas.html#15434

上面说必须在编译链接应用程序的时候指定"-misalign",所以我用了如下命令编译

/opt/SUNWspro/bin/cc -D_KERNEL -misalign -c abc.c
/usr/ccs/bin/ld -r -o abc abc.o

但是我不知道该如何在链接时指定"-misalign"。使用的是"/usr/ccs/bin/ld"。

A: Casper H.S. Dik - Network Security Engineer <Casper.Dik@Holland.Sun.Com>

"-misalign"仅仅用于应用程序,无法应用到内核编程中。"-misalign"使得编译
获得的代码增加了一些runtime glue,它们将指示内核模拟unaligned load(慢)。
作为内核编程,没有等效技术。

Q: 使用#pragma pack()是因为需要读取来自Windows客户端的报文,对端使用
#pragma pack(1)压缩了所使用的数据结构

#pragma pack(1)
typedef struct pkt_hdr_struct
{
uint8_t pkt_ver;
uint32_t pkt_type;
uint32_t pkt_len;
} pkt_hdr_t;
#pragma pack()

为了采用这个结构读取网络数据,Solaris端的服务程序需要强制转换匹配该结构,
但是一旦企图读取紧接在pkt_ver成员之后的pkt_type成员,崩溃了。尝试过其他
办法,首先用一个字符指针读取第一个字节,然后指针增一,把该指针强制类型
转换成( uint32_t * ),然后读取数据,依然崩溃。

此外,是否意味着无法在内核模块编程中使用#pragma pack()

A: Ed L Cashin <ecashin@coe.uga.edu>

我想你可以单独写一个pkt_header_read()函数,单字节读取然后拼装成相应的数
据类型。如果你想避免函数调用,可以使用"inline"关键字。

A: Casper H.S. Dik - Network Security Engineer <Casper.Dik@Holland.Sun.Com>

你是否意识到pkt_hdr_t结构使得你必须自己转换字节序(对端是x86平台)
我不认为#pragma pack()是最好的解决办法,考虑定义如下结构

struct phs
{
char ver;
char type[4];
char len[4];
}

采用memcpy()读取数据

memcpy( &phs.type[0], &pkt.pkt_type, 4 );

A: Andrew Gabriel <andrew@cucumber.demon.co.uk>

采用字符指针是正确的,但是你犯了个错误,编写如下函数

int read_misaligned_int ( int *iptr )
{
int i;
int value;
char *ptr = ( char * )iptr;
char *vptr = ( char * )&value;

for ( i = 0; i < sizeof( int ); i++ )
{
*vptr++ = *ptr++;
}

return( value );
}

此外,既然你提到对端是x86平台,可能还需要考虑字节序转换的问题

A: W. Richard Stevens <1999年逝世,享年49岁>

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -s -o byte_order byte_order.c
*/
#include <stdio.h>
#include <stdlib.h>

/*
* return value:
* 1 big-endian
* 2 little-endian
* 3 unknow
* 4 sizeof( unsigned short int ) != 2
*/
static int byte_order ( void )
{
union
{
unsigned short int s;
unsigned char c[ sizeof( unsigned short int ) ];
} un;

un.s = 0x0201;
if ( 2 == sizeof( unsigned short int ) )
{
if ( ( 2 == un.c[0] ) && ( 1 == un.c[1] ) )
{
puts( "big-endian" );
return( 1 );
}
else if ( ( 1 == un.c[0] ) && ( 2 == un.c[1] ) )
{
puts( "little-endian" );
return( 2 );
}
else
{
puts( "unknow" );
return( 3 );
}
}
else
{
printf( "sizeof( unsigned short int ) = %u/n", ( unsigned int )sizeof(
unsigned short int ) );
return( 4 );
}
return( 3 );
} /* end of byte_order */

int main ( int argc, char * argv[] )
{
printf( "byte_order() = %d/n", byte_order() );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

D: CERNET 华中地区网络中心 程序设计版 集体讨论汇总

为了解决Unix自定义结构在GCC优化编译中对齐问题,一般解决办法是用如下宏封装
自定义结构

#pragma pack(1)

struct my_arphdr
{
};

#pragma pack()

如果是SPARC/Solaris,还可以这样

struct my_arphdr
{
} __attribute__ ((packed));

两种办法其实都可以用在Unix系统/GCC编译器中。

D: mbuf@smth

关于结构中字节对齐问题,相应编译器选项为

GCC/G++ : -fpack-struct
Sun Workshop cc/CC : -misalign

最好不这样做,会大大降低程序效率,特别在某些架构中。应该尝试用位操作来处理。

D: Unknown@smth

GCC可以这么解决

#ifdef __GCC__
#define PACKED __attribute__((__packed__))
#else
#define PACKED
#endif

struct msg
{
u_int16_t PACKED first;
...
};

VC中#include <pshpack1.h>即可,与之相应的还有pshpack2.h、pshpack4.h。

A: gfh_nuaa

DEC : #pragma pack(1)
SUN : #pragma pack(1)
AIX : 编译时 -q align=packed
HP-UX : #pragma pack 1

D: Joe Durusau

在 Visual C++ 中,使用 "-ZP1" 就可以让编译器对自定义结构进行单字节对齐,实
际就是取消了对齐优化。

A: law@bbs.apue.net 2001-12-20 13:09

1) 结构内部成员的pack

struct foo
{
char a;
int b __attribute__ ((packed));
};

2) 整个结构的pack

struct foo
{
char a;
int b;
}__attribute__ ((packed));

3) 文件范围的pack

#pragma pack(1)

struct foo
{
char a;
int b;
};

... ...

4) 编译选项的pack

-fpack-struct

但这是最危险的做法,因为这样做可能会使库函数和你的程序对结构内成员的偏移理
解不一致。

Q: 谁支持push/pop这个用法

#pragma pack(push)
#pragma pack(n)
... ...
#pragma pack(pop)

A: law@bbs.apue.net

我没见过这个写法,VC和GCC都是这样写的

#pragma (push, N) // 把原来align设置压栈,并设新的pack为N
#pragma (pop) // align设置弹栈

假设有如下定义:

int x __attribute__ ((aligned (16))) = 0;

编译器将在16字节边界上分配全局变量x的空间。

8.5 kvm编程举例: 如何编程读取shmsys:shminfo_shmmax的值

Q: 为了避免shmget()不必要的失败,想在C代码中获取shmsys:shminfo_shmmax的值。
但是不能读取/etc/system,那样很不可靠。

set shmsys:shminfo_shmmax = 0x2000000

A: <hume.spamfilter@bofh.halifax.ns.ca>

首先执行如下shell命令
# echo 'shminfo_shmmax/D' | adb -k (SPARC/Solaris 2.6 32-bit kernel mode)
physmem fddb
shminfo_shmmax:
shminfo_shmmax: 134217728

于是我们可以编写如下C代码

--------------------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <kvm.h>
#include <nlist.h>
#include <fcntl.h>

#define KERN_NAMELIST "/dev/ksyms"
/*
* should not be /dev/kmem
*/
#define KERN_CORE "/dev/mem"

int main ( int argc, char * argv[] )
{
kvm_t * krn = NULL;
struct nlist nms[2];
unsigned int val;

if ( ( krn = kvm_open( KERN_NAMELIST, KERN_CORE, NULL, O_RDONLY, argv[0] )
) == NULL )
{
exit( -1 );
}
nms[0].n_name = "shminfo_shmmax";
nms[0].n_value = 0;
nms[0].n_type = 0;
nms[1].n_name = NULL;
nms[1].n_value = 0;
nms[1].n_type = 0;
if ( kvm_nlist( krn, nms ) == 0 )
{
if ( nms[0].n_type != 0 )
{
if ( kvm_read( krn, nms[0].n_value, ( char * )&val, sizeof( val )
) != 4 )
{
fprintf( stderr, "Unable to fetch shminfo_shmmax./n" );
}
else
{
fprintf( stdout, "shminfo_shmmax = %ld/n", val );
}
}
else
{
fprintf( stderr, "Unable to fetch shminfo_shmmax./n" );
}
}
kvm_close( krn );
exit( 0 );
} /* end of main */
--------------------------------------------------------------------------

关于kvm_*()系列函数,可以man -s 3k kvm_nlist等等,但是Sun强烈反对利用
kvm_*()函数,几乎没有兼容性、可移植性可言。

个人不推荐在大型应用软件中使用kvm_*()系列函数。有些可配置系统参数可以通过
sysconf(3C)获取。

8.6 如何得到非局部变量列表

Q: 什么工具可以从目标文件中提取非局部变量列表

A: Donald McLachlan <don@mars.dgrc.crc.ca>

最简单的就是nm,假设你有一个目标文件(或者已链接过的可执行文件),nm -g将显
示所有"全局"变量。下面是一个Solaris的例子:

--------------------------------------------------------------------------
/*
* gcc -o junk junk.c
*/

int var1;
static int var2;

int main ( void )
{
int var3;

return( 0 );
} /* end of main */
--------------------------------------------------------------------------

$ nm -g junk

junk:

[Index] Value Size Type Bind Other Shndx Name

[66] | 133640| 0|OBJT |GLOB |0 |15 |_DYNAMIC
[61] | 133496| 0|OBJT |GLOB |0 |13 |_GLOBAL_OFFSET_TABLE_
[71] | 133528| 0|OBJT |GLOB |0 |14 |_PROCEDURE_LINKAGE_TABLE_
[69] | 0| 0|NOTY |WEAK |0 |UNDEF |__deregister_frame_info
[60] | 0| 0|NOTY |WEAK |0 |UNDEF |__register_frame_info
[70] | 133836| 0|OBJT |GLOB |0 |19 |_edata
[59] | 133872| 0|OBJT |GLOB |0 |20 |_end
[58] | 133864| 4|OBJT |GLOB |0 |20 |_environ
[72] | 67960| 0|OBJT |GLOB |0 |12 |_etext
[67] | 133600| 0|FUNC |GLOB |0 |UNDEF |_exit
[75] | 67936| 20|FUNC |GLOB |0 |11 |_fini
[64] | 67908| 28|FUNC |GLOB |0 |10 |_init
[73] | 67956| 4|OBJT |GLOB |0 |12 |_lib_version
[57] | 67380| 116|FUNC |GLOB |0 |9 |_start
[62] | 133576| 0|FUNC |GLOB |0 |UNDEF |atexit
[68] | 133864| 4|OBJT |WEAK |0 |20 |environ
[63] | 133588| 0|FUNC |GLOB |0 |UNDEF |exit
[74] | 67784| 24|FUNC |GLOB |0 |9 |main
[65] | 133868| 4|OBJT |GLOB |0 |20 |var1
$

注意到var2这样的"静态全局变量",由于仅仅在单个源文件中有效,nm -g并未显示
它。如果不指定-g选项,将显示var2(当然会显示更多垃圾信息)。

$ nm junk

junk:

[Index] Value Size Type Bind Other Shndx Name

... ...
[65] | 133868| 4|OBJT |GLOB |0 |20 |var1
[46] | 133860| 4|OBJT |LOCL |0 |20 |var2
$

8.7 内核可加载模块引用了无法解析的符号

8.8 如何单独获得Solaris编译环境

Q: 我需要安装哪些包

A: Seán Boran <sean@boran.com>

需要下列Solaris安装包:
SUNWbtool、SUNWsprot、SUNWtoo、SUNWhea、SUNWarc、SUNWlibm、SUNWlibms

可以用pkginfo [-l]检查是否安装了这些包

$ pkginfo SUNWbtool SUNWsprot SUNWtoo SUNWhea SUNWarc SUNWlibm SUNWlibms
system SUNWarc Archive Libraries
system SUNWbtool CCS tools bundled with SunOS
system SUNWhea SunOS Header Files
system SUNWlibm Sun WorkShop Bundled libm
system SUNWlibms Sun WorkShop Bundled shared libm
system SUNWsprot Solaris Bundled tools
system SUNWtoo Programming Tools
$

可以从Solaris CD中单独安装缺少的包(pkgadd)

象make这样的工具安装在/usr/ccs/bin,增加到$PATH环境变量中。但是这个make和
某些工具相冲突,比如BIND,此时应该安装GNU make,确认GNU make的搜索路径位于
/usr/ccs/bin/make之前。另外,$PATH环境变量中/usr/ccs/bin应该位于/usr/ucb之
前。

8.9 如何获取Solaris内核可调参数列表

Q: 谁有Solaris内核可调参数列表

A: Andrew Garman <andrew_garman@ins.com>

执行

/usr/xpg4/bin/nm /platform/sun4u/kernel/unix | egrep 'OBJT /|GLOB' | more

显示结果中部分为Solaris内核可调参数,另外一些非可调内核参数。可以用ndd获取、
设置网络相关参数。

D: scz <scz@nsfocus.com>

可以考虑

/usr/ccs/bin/nm -nx /dev/ksyms | egrep 'OBJT /|GLOB' | more

不知道二者区别何在?第二个报告内容应该包含了后来动态加载内核模块输出的符号,
第一个才对应基本内核输出的符号。

8.10 如何获取自Unix纪元以来的秒数,如何转换成可理解的表达方式

8.11 如何页边界对齐式分配内存

Q: 我希望在页边界上分配大块内存,要求普通用户、非特权进程亦能使用此技术。
在mmap(2)手册页中没有明确表明返回地址边界对齐。它提到可以指定起始地址以
保证页边界对齐,但没有说明如果由系统选定起始地址时是否也是页边界对齐的。

MAP_ANON并非所有系统都支持,我需要在Solaris 2.x上运行。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

mmap(2)即可满足要求。某些系统提供了valloc或者memalign,但它们的实现机制是,
分配超过请求大小的内存,然后调整之,这相当浪费。

mmap(2)应该始终是页边界对齐的。

在那些不支持 MAP_ANON 的系统上,打开/dev/zero获取句柄,传递给mmap(2),效果
是一样的。

mmap(2)的可移植性足够好,不过"分配超过请求大小的内存并调整之"可能更具有可
移植性。

8.12 Solaris下究竟如何使用setuid/seteuid/setreuid

D: tt <warning3@nsfocus.com> 2001-06-07 14:04

如果一个Solaris下的程序setuid-to-<any>,应该尽量使用setreuid()永久放弃特权,
而不是setuid(),因为这样最通用。尤其当setuid-to-<not root>的时候,setuid()
根本无法永久放弃特权。

如果一个Solaris下的程序setgid-to-<any>,应该尽量使用setregid()永久放弃特权,
而不是setgid(),因为这样最通用。尤其当整个过程中无法满足EUID为0的时候,
setgid()根本无法永久放弃特权。

假设一个Solaris下的程序同时setuid、setgid过,较理想的释放顺序应该是先释放
setgid特权,后释放setuid特权。

据tt报告,Linux下setuid()实现和Solaris明显不同,无论如何都同时设置RUID、
EUID、SUID(假设权限允许),注意区分不同系统下系统调用setuid()的不同表现。

D: tt <warning3@nsfocus.com> 2003-05-22 10:42

对于一个Solaris下setuid-to-root程序,假设有如下代码片段:

[1] ... ...
[2] seteuid( getuid() );
[3] ... ...
[4] execve( ... ... );

在[1]处获得控制权就没什么好说的了。如果能在[3]处获得控制权,由于SUID的存在,
可以执行"seteuid(0)/setuid(0)"重获root权限。但是,如果只能在[4]加载的进程
里获得控制权,你无法重获root权限。因为执行到[4]时,RUID、EUID非0,只有SUID
为0。而execve()加载的进程里RUID、EUID非0,SUID会被设置成当前的EUID,也非0。
这个问题以前一直没有注意到。

8.13 compile()和step()怎么用

Q: 我知道这两个函数是Solaris对正则表达式的支持函数,可到底怎么用呢?

A: microcat <rotm@263.net>

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o reg reg.c -lgen
*/
#include <stdio.h>
#include <stdlib.h>
#include <regexpr.h>

int main ( int argc, char * argv[] )
{
char * expbuf = NULL;

if ( ( expbuf = compile( argv[1], NULL, NULL ) ) == NULL )
{
exit( EXIT_FAILURE );
}
if ( step( argv[2], expbuf ) )
{
printf( "Match at: %s/n", loc1 );
}
else
{
printf( "No match./n" );
}
free( expbuf );
exit( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

$ ./reg '^.*inetd$' '/usr/sbin/inetd'
Match at: /usr/sbin/inetd
$

8.14 Solaris系统中如何检查内存泄露、腐烂

A: Sun Microsystems 2001-04-25

Q: 在一台Sun服务器上运行了好多程序,运行一段日子后发现内存空闲少了几百兆,
基本上都用光了,怎么知道是那些程序干的

A: lose@水木清华 2002-04-03 10:55

既然已经少了几百兆,那么看一下哪个程序占的内存最大,运行
/usr/dt/bin/sdtprocess,对这个进程进行采样,跑一小时或一天看看内存是否一直
在增加。如果是,然后用purify去找问题出在哪里了。purify并不能保证所有的问题
都能检查出来,所以你还要用上面的方法再检查。

8.15 How to enable microstate accounting in order to use gethrvtime(3C)

A: Sun Microsystems 2000-05-15

库函数gethrvtime(3C)用于获取当前LWP的执行时间,以纳秒(10亿分之一秒)为单位。
但是为了使用gethrvtime(3C)库函数,必须事先enable microstate accounting。参
看ptime(1)手册页。

下面利用ioctl(2)操作procfs,编程实现。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o gethrvtime gethrvtime.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/time.h>
#include <fcntl.h>
#include <sys/procfs.h>
#include <unistd.h>
#include <stropts.h>

static int ms_set ( int onoff )
{
char buffer[80];
int fd;
int state = PR_MSACCT;

sprintf( buffer, "/proc/%d", ( int )getpid() );
if ( ( fd = open( buffer, O_RDWR ) ) == -1 )
{
perror( "open" );
return( EXIT_FAILURE );
}
if ( ioctl( fd, ( onoff ? PIOCSET : PIOCRESET ), &state ) == -1 )
{
perror( "PIOCSET/PIOCRESET" );
close( fd );
return( EXIT_FAILURE );
}
close( fd );
return( EXIT_SUCCESS );
}

int main ( int argc, char * argv[] )
{
hrtime_t start, end;
int i, iters = 10000;

ms_set( 1 );
start = gethrvtime();
for ( i = 0; i < iters; i++ )
{
getpid();
}
end = gethrvtime();
fprintf( stderr, "start = %lld, end = %lld/n", start, end );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

8.16 如何让普通用户可以绑定[1, 1023]闭区间上的特权端口

8.17 SPARC/Solaris 7 64-bit kernel mode下dumpadm(1M)手册页

8.18

9. 图形界面相关问题

9.1 如何避免进入Solaris的图形界面

Q: 我想让console保持在字符模式下,该如何做

A: Darren Dunham <ddunham@redwood.taos.com>

这里有一份很好的FAQ,http://www.wins.uva.nl/pub/solaris/solaris2.html

下文引自http://www.science.uva.nl/pub/solar...ris2.html#q3.54

如何允许/禁止dtlogin?

是否启动dtlogin可以用/usr/dt/bin/dtconfig命令进行设置,不带任何参数执行该
命令,提示如下:

/usr/dt/bin/dtconfig -d (disable auto-start)
/usr/dt/bin/dtconfig -e (enable auto-start)
/usr/dt/bin/dtconfig -kill (kill dtlogin)
/usr/dt/bin/dtconfig -reset (reset dtlogin)
/usr/dt/bin/dtconfig -p (printer action update)
/usr/dt/bin/dtconfig -inetd (inetd.conf /usr/dt daemons)
/usr/dt/bin/dtconfig -inetd.ow (inetd.conf /usr/openwin daemons)

如果绝大多数时间你并不想关闭图形模式,可以在"session"菜单上选择
"command line login"。

A: <lucifer@nospam.org>

更省事的办法是
cd /etc/rc2.d
mv S99dtlogin s99dtlogin

Q: Solaris CDE窗口的启动与关闭

A: tenia@一塌糊涂 1999-11-03

用/usr/dt/bin/dtlogin

dtlogin -daemon 从命令行启动注册窗口
dtlogin -e 使系统自动启动注册窗口
dtlogin -d 取消自动启动
dtlogin -kill 杀掉注册窗口

9.2 Solaris 7的锁屏

Q: Solaris 7中哪个进程负责锁屏效应。7以前的版本,某些人在console登录后锁屏,
回家前忘记取消锁屏,我简单地杀掉xlock进程即可。但是我不知道Solaris 7中
该怎么做。看了看dtsession和dtscreen的一些东西,但是无论我杀掉二者中哪个
进程,console挂起在黑屏的无限循环中,只有鼠标光标可见

A: <buck_naked@NOiname.SPAMcom>

应该是dtscreen

9.3 如何调整键盘重复率

Q: Ultra 5 Solaris 8 如何设置键盘重复率?我想设置重复率到最大,延迟到最小。

A: Alan Coopersmith <alanc@alum.calberkeley.org>
http://soar.Berkeley.EDU/~alanc/

如果是root想对系统中所有用户做此修改,编辑/etc/dt/config/Xservers,增加
-ar1 和 -ar2选项。如果/etc/dt/config/Xservers不存在,从
/usr/dt/config/Xservers复制一份过来。

如果不是root,仅仅想修改自己的配置,用/usr/openwin/bin/accessx配置键盘和鼠
标参数。

man -M /usr/openwin/man Xsun

-ar1 milliseconds

这么多毫秒后按键开始自动重复。缺省500毫秒。参数对于x86或者PowerPC
无效。

-ar2 milliseconds

两次自动重复之间的时间间隔(毫秒单位)。缺省50毫秒。参数对于x86或者
PowerPC无效。

man -M /usr/openwin/man accessx

9.4 如何拔掉键盘继续运行Solaris

Q: 我这里的E250/E3500装了Solaris后键盘都不能拔掉,一拔掉就进入OK状态。而老
式的SparcServer 1000E是可以不要键盘运行的,不知道要在哪里设置才能够不要
键盘运行?

Q: 这里是一台Sun Ultra 5,拔掉键盘后,系统停止响应

A: J.Keil

拔掉键盘导致Ultra 5的console设备侦测到一次BREAK条件。BREAK条件将中断操作系
统,使系统进入OBP(open boot prom)监视状态。如果启动内核时使用了kadb内核调
试器,BREAK条件使系统进入kadb调试状态。

参看kbd(1)手册页,有几种办法禁止这种行为:

a. 永久办法

vi /etc/default/kbd

KEYBOARD_ABORT=disable

/usr/bin/kbd -i (不用重新启动机器)

b. 临时办法

/usr/bin/kbd -a disable
与之对应的就是
/usr/bin/kbd -a enable

c. 看BSM的时候找到的另外一种解决办法,在/etc/system文件中增加如下行

set abort_enable = 0

A: dkoleary@mediaone.net 2001-06-02 22:09

用kbd(1)命令

kbd [ enable | disable | alternate ]

enable : 允许 STOP-A
disable : 禁止 STOP-A
alternate : 允许拔掉键盘,但不禁止 STOP-A

为了使用 alternate 选项,需要安装下列补丁

Solaris 2.6 105924-10
Solaris 7 107589-03

9.5 Solaris下如何设置显卡分辨率

A: kougar@smth.org

/etc/openwin/server/etc/OWconfig
/usr/openwin/server/etc/OWconfig

A: hycan@smth.org

/usr/sbin/m64config -prconf -propt
/usr/sbin/m64config -res '?'

ls -l /dev/fb (一个符号链接)

ls -l /dev/fbs/m640 (一个符号链接)
prtconf -F (Return the device pathname of the console frame buffer)
m64config -res 1152x900x76 -depth 8

ls -l /dev/fbs/ffb0 (一个符号链接)
/usr/sbin/ffbconfig -prconf -propt (当前设置1152x900x76x8)

参看m64config(1M)、ffbconfig(1M)手册页

9.6 Solaris下如何设置显示刷新率

A: CERNET 华中地区网络中心 UNIX版 domyself 2001-08-16

除了m64config(1M),还有一种办法,就是进入OBP状态设置分辨率、刷新率。进入
OBP状态至少有两种方法

# sync <-- 同步文件系统,准备重启
# init 0 <-- 关闭系统后将停留在OBP状态,也就是ok提示符下

其实我们最常用的做法是Stop-A进入OBP状态

ok> show-displays

这里可以看到当前的显示设备,如果你只有一个显示设备,那么这里有两个提示,选
择a就选择了当前显示设备,选择q表示退出,你只能选择a。选择之后可以Ctrl-Y输
入那个很长的设备路径全名。

ok> dev <设备路径全名> (用Ctrl-Y输入)

选择并设置成当前结点,后面的words命令只处理当前结点,不接受指定

ok> pwd (验证当前路径是否正确)
ok> words

列出当前结点的方法名,分辨率和刷新率是作为方法提供的。比如你可能看到

r1024x768x77x24
r1152x900x76x8

注意,数字前面有一个小写的'r',表示resolution。假设我们以前的设置是
1152x900x76x8,现在想改成1024x768x77x24,用如下命令

ok> setenv output-device screen:r1024x768x77x24
ok> boot -r

再次注意,"screen:"之后指定的是方法名,也就是说前面有那个小写的'r'。你用
words看到什么就指定什么。自己估计显存大小,分辨率、刷新率、颜色深度是相互
制约的。最好不要自己调节这些参数,很容易损坏显示设备。OBP状态下设置分辨率、
刷新率的命令普遍描述如下

ok> setenv output-device <device-path>:<resolution>

上面screen是个别名,可以用devalias看到本来的设备路径全名,也就是
show-displays看到的那个设备路径全名。指定设备路径全名也可以,如果你撑着了
的话。

ok> devalias (检查设备别名)

启动之后可以用如下命令检查当前设置

# m64config -prconf | grep Current
Current resolution setting: 1024x768x77
Current depth: 24
#

警告:和调节PC机所配显示设备一样,这种调节具有破坏性,务必小心。对于高版本
的Solaris,建议使用m64config等工具调节显示刷新率,低版本Solaris才考
虑进入OBP状态设置。

A: 水木清华 humvee

x86/Solaris下可用kdmconfig

9.7 在PC X Server上使用中文

Q: 我用WinaXe Plus 6.2做X Server,远程登录SPARC/Solaris 8,经常看到的是乱
码,不得已只好用英文方式登录,有何好的解决办法

A: watercloud <watercloud@nsfocus.com> 2001-01-07

其实能支持中文,只是默认没有相应字体而已,但此时Unix Server上不是有相应字
体吗?拿过来就有了。

cd /usr/openwin/lib/locale/zh/X11/fonts/75dpi
tar cvf /tmp/solaris.tar *

FTP取回到PC,比如解压到WinaXe_Plus/FONTS/solaris目录下,

1) 重命名fonts.alias为fonts.ali
2) 将该目录下的.Z压缩文件都解开,一堆.pcf文件
3) 修改fonts.dir,将其中第一列的文件名改成不带.Z的
4) 运行XSettings,设置X Server的字体路径,将该目录添加进去
5) 运行XSession启动PC X Server,通过XDMCP登录Solaris 8
6) /opt/SUNWspro/WS5.0/bin/workshop启动无误

注意,这里有一个到PC的TCP连接,如有防火墙设置,请自行修改。此外,有必要提
醒的是,PC X Server缺省安装后几乎没有访问控制,历史上那些著名的X攻击......

A: knightmare@smth

还有一种方法就是xfs server。在目标主机上启动xfs server,在PC X Server中指
定目标主机提供的xfs server,这样就可以显示中文了。

9.8 如何让Solaris Console保持在字符登录界面,同时可以远程使用PC X Server

Q: 我在VMware Workstation 3.0上装了一个x86/Solaris 8,想让console保持在字
符登录界面,同时启动PC X Server通过XDMCP远程登录CDE,不成功

A: 小四 <scz@nsfocus.com> 2002-07-03 21:13

执行/usr/dt/bin/dtconfig -d,这样console保持在字符登录界面。需要远程使用
CDE时,手工执行"/usr/dt/bin/dtlogin -daemon &"即可。

检查/etc/rc2.d/S99dtlogin是否存在,如果不存在,执行/usr/dt/bin/dtconfig -e
之后会看到这个启动脚本(反之如果执行/usr/dt/bin/dtconfig -d,将删除该启动脚
本),接着执行/etc/rc2.d/S99dtlogin stop;/etc/rc2.d/S99dtlogin start即可。
观察这个脚本,实际执行了"/usr/dt/bin/dtlogin -daemon &"

问题在于如果运行了该脚本,console将僵死在dtlogin登录界面处,无法正常以字符
模式登录系统。所以不建议允许自动启动/etc/rc2.d/S99dtlogin,可以改名后在需
要时手工启动。

执行/usr/dt/bin/dtconfig -inetd后,将在/etc/inetd.conf中生成三个入口项
/usr/dt/bin/dtspcd、/usr/dt/bin/rpc.cmsd、/usr/dt/bin/rpc.ttdbserverd,其
中第一个与在console上启动CDE相关,在6112/tcp上侦听。不过这三个入口项屏敝后
与远程登录CDE无关。

XDMCP侦听在177/UDP,在Solaris上执行如下命令

# netstat -na -f inet -P udp | grep 177

10. 网卡相关问题

10.0 怎样将第二块网卡名改成hme0

Q: 主板上的网卡坏了,新插一块,但名称怎么改过来

A: lisuit from gceclub.sun.com.cn 2002-10-3 22:12

编辑/etc/path_to_inst文件,查找hme字样,将instance number为0的改为1,1改为
0,然后reboot -- -r。参看path_to_inst(4)手册页。

10.1 如何在程序中获取本机MAC地址

Q: 如何在C代码中获取本机MAC地址,我用strace跟踪ifconfig

ioctl(4, SIOCGIFHWADDR, 0xbffffb80) = 0
ioctl(4, SIOCGIFADDR, 0xbffffb80) = 0
ioctl(4, SIOCGIFBRDADDR, 0xbffffb80) = 0
ioctl(4, SIOCGIFNETMASK, 0xbffffb80) = 0

D: Unix Programmer

用gethostname()/gethostbyname()依赖于本机的域名解析系统,比如/etc/hosts文
件、/etc/nsswitch.conf文件、/etc/resolv.conf文件。这样获取本机IP是不可靠的。
如果/etc/hosts文件中没有指定本机IP,则依赖DNS是否配置了PTR资源记录。可靠的
办法应该是strace ifconfig、truss ifconfig,实际就是照ifconfig的实现去获取
本机IP。

A: David Peter <dave.peter@eu.citrix.com>

strace是Linux下的工具,由于HP-UX 10.20的ioctl不支持SIOCGIFHWADDR,可能需要
DLPI接口或者针对/dev/lan0的NETSTAT ioctl,为了使用NETSTAT ioctl还需要重启
动,而且HP不赞成继续使用NETSTAT ioctl,HP-UX 11.00不再支持。

根据手头一个古老的工具,Digital Unix下ioctl支持SIOCRPHYSADDR。至于SGI上的
IRIX,我想可能需要一个原始套接字,比如:

s = socket( PF_RAW, SOCK_RAW, RAWPROTO_SNOOP )

A: Floyd Davidson <floyd@ptialaska.net>

--------------------------------------------------------------------------
/*
* display info about network interfaces
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>

/*
* 为什么要这样定义宏,直接做强加型转换不好么
*/
#define inaddrr( x ) ( *( struct in_addr * )&ifr->x[ sizeof( sa.sin_port ) ] )
/*
* 注意,这里IFRSIZE是变化的,依赖于size的取值
*/
#define IFRSIZE ( ( int )( size * sizeof( struct ifreq ) ) )

int main ( void )
{
unsigned char * u;
int sockfd, size = 1;
struct ifreq * ifr;
struct ifconf ifc;
struct sockaddr_in sa;

if ( 0 > ( sockfd = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP ) ) )
{
fprintf( stderr, "Cannot open socket./n" );
exit( EXIT_FAILURE );
}
ifc.ifc_req = NULL;
do
{
++size;
/*
* realloc buffer size until no overflow occurs
*/
if ( NULL == ( ifc.ifc_req = realloc( ifc.ifc_req, IFRSIZE ) ) )
{
fprintf( stderr, "Out of memory./n" );
exit( EXIT_FAILURE );
}
ifc.ifc_len = IFRSIZE;
if ( ioctl( sockfd, SIOCGIFCONF, &ifc ) )
{
perror( "ioctl SIOCFIFCONF" );
exit( EXIT_FAILURE );
}
} while ( IFRSIZE <= ifc.ifc_len );
ifr = ifc.ifc_req;
for ( ; ( char * )ifr < ( char * )ifc.ifc_req + ifc.ifc_len; ++ifr )
{
if ( ifr->ifr_addr.sa_data == ( ifr + 1 )->ifr_addr.sa_data )
{
/*
* duplicate, skip it
*/
continue;
}
if ( ioctl( sockfd, SIOCGIFFLAGS, ifr ) )
{
/*
* failed to get flags, skip it
*/
continue;
}
printf( "Interface: %s/n", ifr->ifr_name );
printf( "IP Address: %s/n", inet_ntoa( inaddrr( ifr_addr.sa_data ) ) );
/*
From: David Peter <dave.peter@eu.citrix.com>

This won't work on HP-UX 10.20 as there's no SIOCGIFHWADDR ioctl. You'll
need to use DLPI or the NETSTAT ioctl on /dev/lan0, etc (and you'll need
to be root to use the NETSTAT ioctl. Also this is deprecated and doesn't
work on 11.00).

On Digital Unix you can use the SIOCRPHYSADDR ioctl according to an old
utility I have. Also on SGI I think you need to use a raw socket, e.g. s
= socket(PF_RAW, SOCK_RAW, RAWPROTO_SNOOP)

Dave
*/
if ( 0 == ioctl( sockfd, SIOCGIFHWADDR, ifr ) )
{
/* Select which hardware types to process.
*
* See list in system include file included from
* /usr/include/net/if_arp.h (For example, on
* Linux see file /usr/include/linux/if_arp.h to
* get the list.)
*/
switch ( ifr->ifr_hwaddr.sa_family )
{
case ARPHRD_NETROM:
case ARPHRD_ETHER:
case ARPHRD_PPP:
case ARPHRD_EETHER:
case ARPHRD_IEEE802:
break;
default:
printf( "/n" );
continue;
}
u = ( unsigned char * )&ifr->ifr_addr.sa_data;
if ( u[0] + u[1] + u[2] + u[3] + u[4] + u[5] )
{
/*
* 这里使用%2.2x还不如使用%02x
*/
printf( "HW Address: %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x/n",
u[0], u[1], u[2], u[3], u[4], u[5] );
}
}
/*
* 为什么要做第二个判断,全1的掩码也不是不可能
*/
if ( 0 == ioctl( sockfd, SIOCGIFNETMASK, ifr ) &&
strcmp( "255.255.255.255", inet_ntoa( inaddrr( ifr_addr.sa_data )
) ) )
{
printf( "Netmask: %s/n", inet_ntoa( inaddrr( ifr_addr.sa_data )
) );
}
/*
* 如果设置了广播地址,才继续ioctl
*/
if ( ifr->ifr_flags & IFF_BROADCAST )
{
if ( 0 == ioctl( sockfd, SIOCGIFBRDADDR, ifr ) &&
strcmp( "0.0.0.0", inet_ntoa( inaddrr( ifr_addr.sa_data ) ) ) )
{
printf( "Broadcast: %s/n", inet_ntoa( inaddrr(
ifr_addr.sa_data ) ) );
}
}
if ( 0 == ioctl( sockfd, SIOCGIFMTU, ifr ) )
{
printf( "MTU: %u/n", ifr->ifr_mtu );
}
if ( 0 == ioctl( sockfd, SIOCGIFMETRIC, ifr ) )
{
printf( "Metric: %u/n", ifr->ifr_metric );
}
printf( "/n" );
} /* end of for */
close( sockfd );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

A: scz <scz@nsfocus.com>

上例在Linux下gcc -Wall -pipe -O3 -o getinfo getinfo.c编译即可。

如果用于SPARC/Solaris,需要修改一些地方,最主要的是如何获取本机MAC地址。然
后用如下命令编译

gcc -Wall -pipe -O3 -o getinfo getinfo.c -lsocket -lnsl

--------------------------------------------------------------------------
/*
* display info about network interfaces
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
/*
* 对于Solaris移植,必须包含该头文件
*/
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>

#define inaddrr( x ) ( *( struct in_addr * )&ifr->x[ sizeof( sa.sin_port ) ] )
/*
* 注意,这里IFRSIZE是变化的,依赖于size的取值
*/
#define IFRSIZE ( ( int )( size * sizeof( struct ifreq ) ) )

int main ( void )
{
unsigned char * u;
int sockfd, size = 1;
struct ifreq * ifr;
struct ifconf ifc;
struct sockaddr_in sa;

if ( 0 > ( sockfd = socket( AF_INET, SOCK_DGRAM, IPPROTO_IP ) ) )
{
fprintf( stderr, "Cannot open socket./n" );
exit( EXIT_FAILURE );
}
ifc.ifc_req = NULL;
do
{
++size;
/*
* realloc buffer size until no overflow occurs
*/
if ( NULL == ( ifc.ifc_req = realloc( ifc.ifc_req, IFRSIZE ) ) )
{
fprintf( stderr, "Out of memory./n" );
exit( EXIT_FAILURE );
}
ifc.ifc_len = IFRSIZE;
if ( ioctl( sockfd, SIOCGIFCONF, &ifc ) )
{
perror( "ioctl SIOCFIFCONF" );
exit( EXIT_FAILURE );
}
} while ( IFRSIZE <= ifc.ifc_len );
ifr = ifc.ifc_req;
for ( ; ( char * )ifr < ( char * )ifc.ifc_req + ifc.ifc_len; ++ifr )
{
if ( ifr->ifr_addr.sa_data == ( ifr + 1 )->ifr_addr.sa_data )
{
/*
* duplicate, skip it
*/
continue;
}
if ( ioctl( sockfd, SIOCGIFFLAGS, ifr ) )
{
/*
* failed to get flags, skip it
*/
continue;
}
printf( "Interface: %s/n", ifr->ifr_name );
printf( "IP Address: %s/n", inet_ntoa( inaddrr( ifr_addr.sa_data ) ) );
{
/*
* added by scz <scz@nsfocus.com> 2001-02-22
*/
int s;
struct arpreq arpreq;
struct sockaddr_in * psa;

s = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
if ( s == -1 )
{
/*
* perror( "socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP )" );
*/
}
else
{
memset( &arpreq, 0, sizeof( struct arpreq ) );
psa = ( struct sockaddr_in * )&arpreq.arp_pa;
psa->sin_family = AF_INET;
/*
* IP地址
*/
psa->sin_addr = inaddrr( ifr_addr.sa_data );
if ( ioctl( s, SIOCGARP, &arpreq ) == -1 )
{
/*
* perror( "SIOCGARP" );
*/
}
else
{
u = ( unsigned char * )&arpreq.arp_ha.sa_data;
printf( "HW Address: %02x:%02x:%02x:%02x:%02x:%02x/n",
u[0], u[1], u[2], u[3], u[4], u[5] );
}
close( s );
}

}
if ( 0 == ioctl( sockfd, SIOCGIFNETMASK, ifr ) &&
strcmp( "255.255.255.255", inet_ntoa( inaddrr( ifr_addr.sa_data )
) ) )
{
printf( "Netmask: %s/n", inet_ntoa( inaddrr( ifr_addr.sa_data )
) );
}
/*
* 如果设置了广播地址,才继续ioctl
*/
if ( ifr->ifr_flags & IFF_BROADCAST )
{
if ( 0 == ioctl( sockfd, SIOCGIFBRDADDR, ifr ) &&
strcmp( "0.0.0.0", inet_ntoa( inaddrr( ifr_addr.sa_data ) ) ) )
{
printf( "Broadcast: %s/n", inet_ntoa( inaddrr(
ifr_addr.sa_data ) ) );
}
}
if ( 0 == ioctl( sockfd, SIOCGIFMTU, ifr ) )
{
/*
* 由于MTU是一个int,而Solaris的<net/if.h>联合中没有定义相应成员,
* 所以用ifr_metric(也是一个int)代替,对于联合,这是无所谓的
*/
printf( "MTU: %u/n", ifr->ifr_metric );
}
if ( 0 == ioctl( sockfd, SIOCGIFMETRIC, ifr ) )
{
printf( "Metric: %u/n", ifr->ifr_metric );
}
printf( "/n" );
} /* end of for */
close( sockfd );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

这里演示的技术适用于普通用户,不再需要root权限ifconfig -a查看本机MAC地址。

A: 小四 <scz@nsfocus.com> 2001-12-18 00:49

前面演示了x86/Linux、SPARC/Solaris上如何获取某些网卡配置信息,昨天发现在
x86/FreeBSD上大有不同,确切地说,是Berkeley-derived实现与System V实现有不
少区别,更多细节请参看<<Unix Network Programming>>卷I的16.6节以及17章。重
写代码如下

--------------------------------------------------------------------------
/*
* Copyright (c) 1983, 1993
* The Regents of the University of California. All rights reserved.
* -----------------------------------------------------------------------
*
* gcc -static -Wall -pipe -O3 -o freebsd_ifconfig freebsd_ifconfig.c
* strip freebsd_ifconfig
*
* Fix : NSFocus Security Team
* : http://www.nsfocus.com
* : scz@nsfocus.com
* Date : 2001-12-19 16:27
*/

/************************************************************************
* *
* Head File *
* *
************************************************************************/

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h> /* 使用getopt(3) man -S 3 getopt */
#include <err.h> /* for errx() */
#include <sys/param.h> /* for htonl() */
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/module.h>
#include <sys/linker.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>

/************************************************************************
* *
* Macro *
* *
************************************************************************/

typedef void af_status __P( ( int, struct rt_addrinfo * ) );

/*
* Expand the compacted form of addresses as returned via the
* configuration read via sysctl().
*/
#define ROUNDUP(a) /
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))

#define IFFBITS /
"/020/1UP/2BROADCAST/3DEBUG/4LOOPBACK/7RUNNING" /
"/10NOARP/11PROMISC/13OACTIVE/20MULTICAST"

/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/

static void ether_status ( int s __unused, struct rt_addrinfo *info );
static void in_status ( int s __unused, struct rt_addrinfo *info );
static void rt_xaddrs ( caddr_t cp, caddr_t cplim, struct rt_addrinfo *info );
static void showflags ( unsigned int v, const char *bits );
static void status ( int addrcount, struct sockaddr_dl *sdl,
struct if_msghdr *ifm, struct ifa_msghdr *ifam );
static void usage ( char *arg );

/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/

/*
* Known address families
*/
static const struct afswtch
{
const char *af_name;
short af_af;
af_status *af_status;
} afs[] =
{
{ "inet", AF_INET, in_status, },
{ "ether", AF_LINK, ether_status, },
{ 0, 0, 0, }
};

/*
* 接口名,比如fxp0
*/
static char name[32];
static char *interface = NULL;
static int flags;
static struct ifreq ifr;

/************************************************************************/

/*
* __unused避免Warning信息
*/
static void ether_status ( int s __unused, struct rt_addrinfo *info )
{
char *cp;
int n;
struct sockaddr_dl *sdl = ( struct sockaddr_dl * )info;

/*
* 经过宏处理,cp指向MAC地址
*/
cp = ( char * )LLADDR( sdl );
/*
* 靠成员sdl_alen标识长度
*/
if ( ( n = sdl->sdl_alen ) > 0 )
{
fprintf( stderr, "MAC : " );
while ( --n >= 0 )
{
fprintf( stderr, "%02X%c", *cp++ & 0xff, n > 0 ? ':' : '/n' );
}
}
} /* end of ether_status */

static void in_status ( int s __unused, struct rt_addrinfo *info )
{
struct sockaddr_in *sin, null_sin;

memset( &null_sin, 0, sizeof( null_sin ) );
/*
* IP地址(CIDR)
*
* 参看UNP vol I 图17.8 理解这个函数
*/
sin = ( struct sockaddr_in * )info->rti_info[ RTAX_IFA ];
fprintf( stderr, "IP : %s/", inet_ntoa( sin->sin_addr ) );
/*
* 子网掩码
*/
sin = ( struct sockaddr_in * )info->rti_info[ RTAX_NETMASK ];
if ( !sin )
{
sin = &null_sin;
}
fprintf( stderr, "%s/", inet_ntoa( sin->sin_addr ) );
/*
* 广播地址
*/
sin = ( struct sockaddr_in * )info->rti_info[ RTAX_BRD ];
if ( !sin )
{
sin = &null_sin;
}
fprintf( stderr, "%s/n", inet_ntoa( sin->sin_addr ) );
} /* end of in_status */

/*
* 第二形参指定上限
*
* 参看 UNP vol I 图17.9 的代码理解这里
*/
static void rt_xaddrs ( caddr_t cp, caddr_t cplim, struct rt_addrinfo *info )
{
struct sockaddr *sa;
int i;

/*
* 指针数组清零
*/
memset( info->rti_info, 0, sizeof( info->rti_info ) );
for ( i = 0; ( i < RTAX_MAX ) && ( cp < cplim ); i++ )
{
/*
* rti_addrs是个掩码的概念
*/
if ( ( info->rti_addrs & ( 1 << i ) ) == 0 )
{
continue;
}
/*
* 指针数组赋值
*/
info->rti_info[ i ] = sa = ( struct sockaddr * )cp;
ADVANCE( cp, sa );
}
return;
} /* end of rt_xaddrs */

static void showflags ( unsigned int v, const char *bits )
{
register int i, any = 0;
register char c;

if ( *bits == 8 )
{
fprintf( stderr, "Flags : %#o ", v );
}
else
{
fprintf( stderr, "Flags : %#x ", v );
}
bits++;
fprintf( stderr, "<" );
while ( ( i = *bits++ ) != '/0' )
{
/*
* 这后面是掩码的概念
*/
if ( v & ( 1 << ( i - 1 ) ) )
{
if ( any )
{
fprintf( stderr, "," );
}
any = 1;
for ( ; ( c = *bits ) > 32; bits++ )
{
fprintf( stderr, "%c", c );
}
}
else
{
for ( ; *bits > 32; bits++ )
{
;
}
}
} /* end of while */
fprintf( stderr, ">/n" );
return;
} /* end of showflags */

/*
* Print the status of the interface. If an address family was specified,
* show it and it only; otherwise, show them all.
*/
static void status ( int addrcount, struct sockaddr_dl *sdl,
struct if_msghdr *ifm, struct ifa_msghdr *ifam )
{
struct rt_addrinfo info;
const struct afswtch *afp;
const struct afswtch *p = NULL;
int s;

fprintf( stderr, "/nInterface : %s/n", name );
showflags( flags, IFFBITS );
afp = &afs[0];
ifr.ifr_addr.sa_family = AF_INET;
strncpy( ifr.ifr_name, name, sizeof( ifr.ifr_name ) );
if ( ( s = socket( ifr.ifr_addr.sa_family, SOCK_DGRAM, 0 ) ) < 0 )
{
err( 1, "socket" );
}
if ( ioctl( s, SIOCGIFMETRIC, ( caddr_t )&ifr ) == 0 )
{
fprintf( stderr, "Metric : %d/n", ifr.ifr_metric );
}
if ( ioctl( s, SIOCGIFMTU, ( caddr_t )&ifr ) == 0 )
{
fprintf( stderr, "MTU : %d/n", ifr.ifr_mtu );
}
while ( addrcount > 0 )
{
/*
* rti_addrs是个掩码的概念
*/
info.rti_addrs = ifam->ifam_addrs;
/*
* Expand the compacted addresses
*
* 第二形参指定上限,第一形参指向 struct ifa_msghdr 之后的位置
*/
rt_xaddrs( ( char * )( ifam + 1 ), ( char * )ifam + ifam->ifam_msglen,
&info );
for ( p = afs; p->af_name; p++ )
{
if ( info.rti_info[ RTAX_IFA ]->sa_family == p->af_af )
{
/*
* 一个函数指针
*/
( *p->af_status )( s, &info );
}
}
addrcount--;
ifam = ( struct ifa_msghdr * )( ( char * )ifam + ifam->ifam_msglen );
} /* end of while */
/*
* 第二形参做强加型转换,仅仅是为了统一到struct afswtch中
*/
ether_status( s, ( struct rt_addrinfo * )sdl );
close( s );
return;
} /* end of status */

static void usage ( char *arg )
{
fprintf( stderr, "Usage: %s [-i <interface>]/n", arg );
exit( EXIT_FAILURE );
} /* end of usage */

int main ( int argc, char * argv[] )
{
int c;
int addrcount;
int mib[6];
size_t need = 0;
char *buf = NULL;
char *lim, *next;
struct if_msghdr *ifm;
struct ifa_msghdr *ifam, *nextifam;
struct sockaddr_dl *sdl;

opterr = 0; /* don't want getopt() writing to stderr */
while ( ( c = getopt( argc, argv, "hi:" ) ) != EOF )
{
switch ( c )
{
case 'i':
interface = optarg;
break;
case 'h':
case '?':
usage( argv[0] );
break;
} /* end of switch */
} /* end of while */
argc -= optind;
argv += optind;

/*
* 参看<<Unix Network Programming>> vol I 17章
*/
mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = 0; /* address family */
mib[4] = NET_RT_IFLIST;
mib[5] = 0;

/*
* first get size, we should do the second call,返回值在need中
*/
if ( sysctl( mib, 6, NULL, &need, NULL, 0 ) < 0 )
{
errx( 1, "first sysctl" );
}
if ( ( buf = ( char * )malloc( need ) ) == NULL )
{
errx( 1, "malloc" );
}
if ( sysctl( mib, 6, buf, &need, NULL, 0 ) < 0 )
{
errx( 1, "second sysctl");
}
lim = buf + need;
next = buf;
while ( next < lim )
{
ifm = ( struct if_msghdr * )next;
/*
* 每个接口一个RTM_IFINFO
*/
if ( ifm->ifm_type == RTM_IFINFO )
{
/*
* struct sockaddr_dl 位于 struct if_msghdr 之后
*/
sdl = ( struct sockaddr_dl * )( ifm + 1 );
/*
* flags 位于 struct if_msghdr 之中
*/
flags = ifm->ifm_flags;
}
else
{
fprintf( stderr, "Some error/n" );
exit( EXIT_FAILURE );
}
next += ifm->ifm_msglen;
ifam = NULL;
addrcount = 0;
while ( next < lim )
{
/*
* 参看UNP vol I 图17.13,理解这里的while循环
*/
nextifam = ( struct ifa_msghdr * )next;
/*
* 本接口每个已配置的IP对应一个RTM_NEWADDR
*/
if ( nextifam->ifam_type != RTM_NEWADDR )
{
break;
}
/*
* ifam指向本接口IP链表的第一个位置
*/
if ( ifam == NULL )
{
ifam = nextifam;
}
/*
* 统计IP个数,包括主IP
*/
addrcount++;
next += nextifam->ifam_msglen;
} /* end of while */
/*
* 接口名,比如fxp0,注意这里不以'/0'结尾,靠成员sdl_nlen标识长度
*/
strncpy( name, sdl->sdl_data, sdl->sdl_nlen );
name[ sdl->sdl_nlen ] = '/0';
if ( NULL == interface )
{
/*
* 遍历所有接口
*/
status( addrcount, sdl, ifm, ifam );
}
else
{
if ( strcmp( interface, name ) == 0 )
{
/*
* 检查指定接口
*/
status( addrcount, sdl, ifm, ifam );
}
}
} /* end of while */
free( buf );
return( EXIT_SUCCESS );
} /* end of main */

/************************************************************************/

--------------------------------------------------------------------------

作者:飞猫头衔:版主发布时间:  2004-12-23 18:00:00

第2楼: 
A: Sun Microsystems 2001-01-03

如果不想通过ARP表获取MAC地址,就只能通过DLPI实现。此时必须以root身份运行该
程序。

--------------------------------------------------------------------------
/*
* Usage : <program name> <device name> <instance number>
*
* The program opens the Data Link provider, attaches to the PPA (Physical
* Point of Attachment) & uses the DL_PHYS_ADDR_REQ DLPI primitive to
* request the current physical address which is returned in the
* DL_PHYS_ADDR_ACK message. The MAC address is converted to ASCII format
* using the ether_ntoa(3N) function and printed.
*
* The program needs to be compiled with the "-lsocket -lnsl" flags.
* # gcc -Wall -pipe -O3 -o ether ether.c -lsocket -lnsl
*
* The output of the program will be as shown below.
*
* # ./ether /dev/hme 0
* Mac Address 8:0:20:a8:2e:ac
*
* This program has been tested on SPARC machines running Solaris 2.5.1,
* 2.6, 7 and 8 with the /dev/le and /dev/hme interfaces.
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <sys/errno.h>
#include <unistd.h>
#include <sys/dlpi.h>

#define MAXDLBUF 256

int ppa, fd, flags, errno;
long buffer[ MAXDLBUF ];
struct strbuf ctl;
dl_phys_addr_req_t phys_addr_req;
dl_attach_req_t attach_req;
dl_phys_addr_ack_t * dlpadd;

int main ( int argc, char * argv[] )
{
if ( argc != 3 )
{
printf( "Usage : <program name> <device name> <instance number>/n" );
exit( 1 ) ;
}
if ( geteuid() != 0 )
{
printf( "Must be root./n" );
exit( 1 );
}
if ( ( fd = open( argv[1], O_RDWR, 0 ) ) < 0 )
{
printf( "Open of Device %s failed/n", argv[1] );
exit( 1 );
}
ppa = atoi( argv[2] );
attach_req.dl_primitive = DL_ATTACH_REQ;
attach_req.dl_ppa = ppa;

ctl.maxlen = 0;
ctl.len = sizeof( attach_req );
ctl.buf = ( char * )&attach_req;
flags = 0;

if ( putmsg( fd, &ctl, ( struct strbuf * )NULL, flags ) < 0 )
{
perror( "dlattachreq error/n" );
return( errno );
}

ctl.maxlen = MAXDLBUF;
ctl.len = 0;
ctl.buf = ( char * )&buffer;

if ( getmsg( fd, &ctl, ( struct strbuf * )0, &flags ) < 0 )
{
perror( "dlattachack error/n" );
return( errno );
}

if ( ctl.len > sizeof( dl_ok_ack_t ) )
{
printf( "dlattachokack too long/n" );
exit( -1 );
}
if ( flags != RS_HIPRI )
{
printf( "dlattachokack not RS_HIPRI/n" );
exit( -1 );
}
if ( ctl.len < sizeof( dl_ok_ack_t ) )
{
printf( "dlattachokack too short/n" );
exit( -1 );
}

phys_addr_req.dl_primitive = DL_PHYS_ADDR_REQ;
phys_addr_req.dl_addr_type = DL_CURR_PHYS_ADDR;

ctl.maxlen = 0;
ctl.len = sizeof( phys_addr_req );
ctl.buf = ( char * ) &phys_addr_req;
flags = 0;

if ( putmsg( fd, &ctl, ( struct strbuf * )NULL, flags ) < 0 )
{
perror( "dlphysaddreq error/n" );
return( errno );
}

ctl.maxlen = MAXDLBUF;
ctl.len = 0;
ctl.buf = ( char * ) buffer;

if ( getmsg( fd, &ctl, ( struct strbuf * )0, &flags ) < 0 )
{
perror( "dlinfoack error/n" );
return( errno );
}
if ( flags != RS_HIPRI )
{
printf( "dlgetpyhsaddr not RS_HIPRI/n" );
exit( -1 );
}
if ( ctl.len < sizeof( dl_phys_addr_ack_t ) )
{
printf( "dlgetphysaddr too short/n" );
exit( -1 );
}
dlpadd = ( dl_phys_addr_ack_t * )ctl.buf;
printf( "Mac Address %s/n",
( char * )ether_ntoa( ctl.buf + dlpadd->dl_addr_offset ) );
return( 0 );
} /* end of main */
--------------------------------------------------------------------------

D: scz <scz@nsfocus.com> 2001-11-20 11:46

SPARC/Solaris下只有root用户才可以ifconfig -a看到本机MAC地址,普通用户并不
能这样做,但可以尝试在dmesg输出中查找,由于dmesg使用的数据有可能被破坏,这
个办法并不可靠。

"arp <本机IP地址>"可以看到本机MAC地址,使用的技术实际上就是前面编程演示的

ioctl( s, SIOCGARP, &arpreq )

此外还可以用如下命令获取本机MAC地址

ndd /dev/arp arp_cache_report | grep MYADDR

D: scz 2002-07-05 19:02

对于Solaris 7/8,netstat -np | grep SP 可以看到本机MAC地址、IP地址

$ netstat -np | grep SP | awk '{print $2}'
192.168.5.150
$ netstat -np | grep SP | awk '{print $5}'
00:00:00:03:03:03
$

10.2 如何在Sun工作站上安装3块网卡

Q: 我想在Sun工作站上安装3块网卡,怎么办

A: Santosh

请遵循如下步骤

1) 在Sun工作站上增加网卡

2) 用boot -r启动系统

3) 观察启动信息,确认每块网卡都被识别出来,比如这种信息

PCI-device: network@1,1, hme #0
SUNW,hme0 is /pci@1f,4000/network@1,1

实际中如果每块网卡都被识别出来,有hme0、hme1 和 hme2,当然1和2可能不是
这个名字。

4) 到/etc目录下创建hostname.hme0、hostname.hme1 和 hostname.hme2。在每个文
件中分别指定IP地址,编辑/etc/hosts文件增加相应入口。

5) 重启机器

10.3 如何在Solaris x86上安装网卡驱动

A: James Adkins <jadkins@peregrine.com>

不需要修改"pcn.conf"文件。开始我只是"touch /reconfigure",Solaris x86检测
到了网卡,但是"ifconfig -a"的时候只有loopback接口,于是我尝试如下步骤:

# drvconfig
# devlinks
# touch /reconfigure

重启动后一切ok

D: scz <scz@nsfocus.com> 2003-04-23

在VMware上安装x86/Solaris,有时系统崩溃后网卡工作不正常,我是这样恢复正常
的:

drvconfig
devlinks
touch /reconfigure
halt

去VMware中删除网卡,重启动,再次执行:

drvconfig
devlinks
touch /reconfigure
halt

去VMware中增加网卡,重启动,发现网卡恢复正常。

10.4 Solaris 单网卡多IP(以太网卡别名)

Q: 对于Solaris 2.5.1来说,可以在一块物理网卡上配置多个IP地址

A: Sun Microsystems 1998-03-31

下面以lance ethernet (le0) 设备为例说明

1) 编辑/etc/hosts文件

128.195.10.31 myhost
128.195.10.46 myhost2
128.195.10.78 myhost3

2) 创建/etc/hostname.le0:n文件,注意hostname.le0:0就是hostname.le0

/etc/hostname.le0 (Contains name myhost)
/etc/hostname.le0:1 (Contains name myhost2)
/etc/hostname.le0:2 (Contains name myhost3)

注意这种文件就一行内容,主机名。

3) 如果想立即生效

% ifconfig le0:1 up
% ifconfig le0:1 129.153.76.72
% ifconfig le0:1 down

Q: Solaris 8下如何给一块以太网卡赋予多个IP地址?

A: Vadim V. Kouevda <VKouevda@pcinetgw.is.bear.com>

ifconfig le0 plumb
ifconfig le0 ether 0:1:2:3:4:5
ifconfig le0:1 plumb
ifconfig le0:1 ... up
ifconfig le0:2 plumb
ifconfig le0:2 ... up

到/etc/init.d目录下修改IP地址、子网掩码等设置。

D: scz <scz@nsfocus.com>

有三个文件需要注意,/etc/rcS.d/S30rootusr.sh(/etc/init.d/rootusr)、
/etc/rc2.d/S69inet(/etc/init.d/inetinit)和/etc/rc2.d/S72inetsvc
(/etc/init.d/inetsvc)。

Q: 如何在一块物理网卡上绑定多个IP地址

A: Sun Microsystems 1997-10-27

所谓虚拟网络接口指一个物理接口多个不同IP地址,Solaris允许一个物理网络接口
对应多个逻辑接口,换句话说,即使只有一块网卡,也可以配置多个IP地址。参看
ifconfig(1M)手册页。对于Solaris 2.x,可以在一块网卡上绑定256个不同IP地址。
Sun OS 4.x(Solaris 1.x)不支持。

/usr/sbin/ndd -get /dev/ip ip_addrs_per_if

对于Solaris 2.6,通过ndd可以配置超过256(0-255)个IP地址。

/usr/sbin/ndd -set /dev/ip ip_addrs_per_if 1-8192

将这条命令增加到/etc/rc2.d/S69inet启动脚本中去。

1) 编辑/etc/hosts文件(或者nis host map),为每个虚拟接口增加条目。别忘记修
改NIS、NIS+、DNS数据库。

2) 为每个接口创建/etc/hostname.<interface:#>文件,比如/etc/hostname.le0:1、
hostname.le0:2、hostname.le0:3 ... le0:255。文件内容为单行IP地址或者主
机名。比如创建如下文件

/etc/hostname.le0:1 (不要使用le0:0,那就是le0)
/etc/hostname.le0:2

Solaris 2.5.1下最多1024个虚拟接口。每个文件内容是自己对应的虚拟接口IP地
址或者主机名。

3) 如果使用了子网,应该在/etc/netmasks中增加

network_address netmask

157.145.0.0 255.255.255.0

4) 重启系统

5) ifconfig -a验证之

某些第三方应用程序此时可能会出问题。出于安全考虑,可以

ndd -set /dev/ip ip_forwarding 0
ndd -set /dev/ip ip_strict_dst_multihoming 1

参看RFC1112 - <<Host Extensions for IP Multicasting>>。

如果因为配置虚拟接口出现不期望的路由,考虑手动"route delete"。可以增加一个
启动脚本/etc/rc2.d/S99vif,用于完成这些任务。

对于Solaris 2.6,可能还需要

ndd -set /dev/ip ip_enable_group_ifs 0 (2.6下缺省是1,7下缺省是0)

将这条命令增加到/etc/rc2.d/S69inet启动脚本中去。

Q: 如何创建1024个虚拟网络接口

A: Sun Microsystems 2001-03-22

下面是一个启动脚本举例

1) 在/etc/rc2.d/S69inet的最后增加如下内容

if [ -f /test_up ]
then
/test_up
fi

2) 创建文件"/test_up",使之可执行,增加如下内容

--------------------------------------------------------------------------
#! /bin/ksh

if [ $# -ne 1 ] ; then
echo "Usage: $0 <up | down>"
exit 1
fi

# set this value to the number of logical interfaces you want per physical
# interfaces
# 5意味着五个逻辑接口(不包括那个物理接口)
typeset -i N=5

#set these to the IP addrs you want to configure
typeset -i IP3=192
typeset -i IP2=168
typeset -i IP1=10
typeset -i IP0=102

typeset -i n=1

# set this value to the number of logical interfaces you want per physical
# interfaces
ndd -set /dev/ip ip_addrs_per_if `/usr/bin/expr $N + 1`

# set this to the correct type of physical interface
PIF=hme0

while [ $n -le $N ]
do
addr="$IP3.$IP2.$IP1.$IP0"
ifconfig $PIF:$n plumb
ifconfig $PIF:$n inet $addr netmask 255.255.0.0 broadcast 192.168.255.255 $1
IP0=IP0+1
if [ IP0 -eq 254 ]
then
IP0=1
IP1=IP1+1
if ((IP1==254))
then
IP1=0
IP2=IP2+1
fi
fi
n=n+1
done
--------------------------------------------------------------------------

10.5 如何修改主机名(hostname)

Q: Solaris 2.6下如何修改主机名(hostname)

A: Herve Poussin <poussin@partner-system.com>

需要修改如下文件

/etc/hosts
/etc/hostname.<interface>
/etc/nodename
/etc/net/*/hosts (3 files, man -s 7D ticotsord)

如果你运行在VxVM下,则应该

# vxdctl hostid <new_name>
# vxdctl init <new_name>

10.6 SPARC/Solaris 2.5/2.6/7/8下如何设置网卡100Mb全双工

Q: 我从SPARC连接3Com交换机时,总是使用半双工,如何配置网卡强行使用100Mb全
双工

A: Martin Scerri <martin_scerri@bigfoot.com>

下列回答来自"Sun管理员FAQ 12.3",并且只适合于Solaris 2.5及其以后版本,
Sun OS 4.x及其更早版本不支持hme接口。

一般网卡可以和交换机自动协商使用100Mb全双工,如果协商失败,可能看到诸如
"late collision"一类的消息,出现丢包甚至完全不能工作的现象。为了强行指定使
用某一确定的工作模式,比如100Mb FD,可以用ndd做如下操作:

# 指定操作hme0接口
ndd -set /dev/hme instance 0
# 关闭自动协商
ndd -set /dev/hme adv_autoneg_cap 0
# 打开100Mb FD支持
ndd -set /dev/hme adv_100fdx_cap 1
# 关闭100Mb HD支持
ndd -set /dev/hme adv_100hdx_cap 0
# 关闭10Mb FD支持
ndd -set /dev/hme adv_10fdx_cap 0
# 关闭10Mb HD支持
ndd -set /dev/hme adv_10hdx_cap 0

同样需要在对端(比如交换机)强行指定使用100Mb FD模式。

注意:Fast ethernet hubs 总是使用100Mb HD模式
ethernet hubs 总是使用10Mb HD模式

如果你想强行指定系统中所有hme网卡在启动时进入同一确定模式,可以在
/etc/system文件中设置,下例表示进入100Mbit FD模式:

set hme:hme_adv_autoneg_cap=0
set hme:hme_adv_100fdx_cap=1
set hme:hme_adv_100hdx_cap=0
set hme:hme_adv_10hdx_cap=0
set hme:hme_adv_10fdx_cap=0

A: Andreas.Gouder <Andreas.Gouder@t-online.de>

你可以用如下命令获取当前设置

# ndd -get /dev/hme link_mode

0 半双工
1 全双工

# ndd -get /dev/hme link_status

0 Link Down
1 Link up

# ndd -get /dev/hme link_speed

0 10Mbps
1 100Mbps

10.7 Unix如何对抗ARP欺骗

Q: Solaris的静态ARP表项(arp -s)还是会被动态刷新,我只确认Linux/FreeBSD的静
态ARP表项不会被动态刷新,到底有没有稍微通用点的对抗ARP欺骗攻击的方法。

A: scz <scz@nsfocus.com>

下面以Solaris系统为例说明,其他系统大同小异。

1) 建立静态ARP表

/usr/bin/touch /etc/static_arp_entry
/usr/bin/chown root:root /etc/static_arp_entry
/usr/bin/chmod 600 /etc/static_arp_entry

编辑/etc/static_arp_entry文件,输入类似内容

192.168.8.90 00:00:00:11:11:11

/usr/sbin/arp -s -f /etc/static_arp_entry

可以在/etc/rc2.d/S69inet启动脚本中增加这条命令。参看arp(1M)、arp(7P)手册页
了解更多细节。这种技术对系统影响不大,对网络影响较大,破坏了动态ARP解析过
程。Solaris系统中,静态ARP表不会过期,必须用"arp -d"手动删除。但是,
Solaris系统的静态ARP表项可以被动态刷新,仅仅依靠静态ARP表项并不能对抗ARP欺
骗攻击,相反纵容了ARP欺骗攻击,因为虚假的静态ARP表项不会自动超时消失。当然,
可以考虑利用cron机制补救之。(增加一个crontab)

为了对抗ARP欺骗攻击,对于Solaris系统来说,应该结合"禁止相应网络接口做ARP解
析"和"使用静态ARP表"的设置

2) 禁止某个网络接口做ARP解析(对抗ARP欺骗攻击)

"/sbin/ifconfig hme0 -arp"命令将禁止hme0接口做ARP解析,hme0接口不会发送/接
收ARP报文。必须配合使用静态ARP表,否则无法完成正常网络通信。参看
ifconfig(1M)了解更多细节。假设/etc/rc2.d/S69inet启动脚本中存在如下内容

/sbin/ifconfig hme0 -arp
/usr/sbin/arp -s -f /etc/static_arp_entry

假设/etc/static_arp_entry文件内容如下

192.168.8.90 00:00:00:11:11:11

这里192.168.8.90是一台PWin98,也做静态ARP设置(因为对方不会响应ARP请求报文)

arp -s 192.168.10.6 08-00-20-a8-2e-ac

此时192.168.8.90与192.168.10.6可以正常通信,并且192.168.10.6不受ARP欺骗攻
击的影响。事实上,绝大多数Unix操作系统,都可以结合"禁止相应网络接口做ARP解
析"和"使用静态ARP表"的设置来对抗ARP欺骗攻击。对于Linux/FreeBSD系统,因为其
静态ARP表项(arp -s)不会被动态刷新,所以不需要"禁止相应网络接口做ARP解析"即
可对抗ARP欺骗攻击。

10.8 SPARC/Solaris 2.6/7/8下如何检查网卡混杂模式

10.9 FreeBSD下ifconfig的man手册

10.10 FreeBSD下arp的man手册

10.11 x86/Solaris如何强制设定网卡速率

Q: x86/Solaris,我如何强行指定网卡的工作状态为100Mbps full-duplex,ndd(1M)
不工作

A: CERNET 水木清华 Unix版 inc 2001-10-11 20:29

x86/Solaris下的网卡驱动不支持ndd(1M)设置网卡工作状态,要达到目的,唯一的办
法是通过driver.conf(4)指定。x86/Solaris 8的iprb(7D)手册页建议使用
ForceSpeedDuplex选项。对于其它驱动,参看如下例子

vi /kernel/drv/iprb.conf <-- 用ifconfig -a确认一下

# To force full duplex operation, uncomment the following line:
# full-duplex=1;
#
# To force half duplex operation, uncomment the following line:
# full-duplex=0;
#
# To force 10Mbps operation, uncomment the following line:
# speed=10;
#
# To force 100Mbps operation, uncomment the following line:
# speed=100;

奇怪的是iprb.conf原来没有上面的内容,elxl.conf却有。注意,不同网卡是有区别
的,我试了RealTek RTL8139/8129、3Com 3C905B TX、Intel,只有Intel的可以这样
修改,RealTek RTL8029的我不确定。

D: CERNET 水木清华 Unix版 2001-10-12 10:36

由于x86下网卡驱动不支持ndd(1M)获取网卡状态,被迫使用netstat -k

ifconfig -a 找出网络接口名

netstat -k <interface> | grep ifspeed

某些x86网卡驱动支持,某些不支持,这个办法同样适合于SPARC网卡驱动,虽然后者
可以直接使用ndd(1M)。

10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed

A: 小四 <scz@nsfocus.com> 2001-12-07 17:06

Solaris

# netstat -k hme0 | grep ifspeed

# ndd -get /dev/hme link_mode

0 半双工
1 全双工

# ndd -get /dev/hme link_status

0 Link Down
1 Link up

# ndd -get /dev/hme link_speed

0 10Mbps
1 100Mbps

FreeBSD用ifconfig就可以看到

status: active <-- 网线接到一个HUB上了
status: no carrier <-- 未接网线

A: starw@smth.org

在高版本的Linux系统中net-tools包中有一个mii-tool命令,可以用于检查这些数据,
而不是溶合在ifconfig的输出中。

10.13 x86/FreeBSD 4.3-RELEASE下LINK_ADDR(3)手册页

10.14 traceroute是怎么实现的

Q: traceroute是怎么实现的

A: 小四 <scz@nsfocus.com>

traceroute(对于Windows系列是tracert)通过逐步增加TTL值的方法,发送常规IP分
组来实现。第一次发送分组时TTL为1,第一个路由器接收到该分组之后将TTL值减1,
结果为0,于是就丢弃该分组,并发回一个"TTL超时"的ICMP报文,该报文的源地址是
这第一个路由器。紧接着发送一个TTL为2的分组。注意,traceroute发送常规的UDP
报文到一个不用的UDP端口。当初考虑将路由跟踪的功能加入IP协议本身,定义一个
"路由跟踪"选项,路由器处理带有该选项的IP包时立即发回一个跟踪报文到源站点。
然而这种做法还停留在实验室阶段,因为这需要改变所有已经存在的路由器,而且在
某种程度上与端到端原则相违背。

1) 传统的Unix实现是UDP+ICMP
2) 其实从原理上TCP+ICMP也是可以的,某些Unix系统采用了这种实现
3) Windows另有一种实现,Icmp Echo Request+ICMP

作者:飞猫头衔:版主发布时间:  2004-12-23 18:00:51

第3楼: 
[scz@ /home/scz/src]> echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:.
[scz@ /export/home/scz/src]> myprog_3
path --> ffbeffee [myprog_3]
argv --> ffbefcfc
argv[0] --> ffbefd7c [myprog_3]
environ --> ffbefd04
environ[0] --> ffbefd85 [PWD=/export/home/scz/src]
[scz@ /export/home/scz/src]> ./myprog_3
path --> ffbefff0 [myprog_3] <-- 注意前面反而没有了"./"
argv --> ffbefcfc
argv[0] --> ffbefd7c [./myprog_3]
environ --> ffbefd04
environ[0] --> ffbefd87 [PWD=/export/home/scz/src]
[scz@ /export/home/scz/src]> ../src/myprog_3
path --> ffbeffea [../src/myprog_3]
argv --> ffbefcec
argv[0] --> ffbefd6c [../src/myprog_3]
environ --> ffbefcf4
environ[0] --> ffbefd7c [PWD=/export/home/scz/src]
[scz@ /export/home/scz/src]>

如果我们复制myprog_3到/usr/bin下去,再次执行myprog_3,可以看到如下输出

[scz@ /export/home/scz/src]> myprog_3
path --> ffbeffe9 [/usr/bin/myprog_3] <-- 注意这里
argv --> ffbefcf4
argv[0] --> ffbefd74 [myprog_3]
environ --> ffbefcfc
environ[0] --> ffbefd7d [PWD=/export/home/scz/src]
byteArray [ 256 bytes ] ---->
00000000 55 6C 74 72 61 2D 35 5F-31 30 00 53 48 4C 56 4C Ultra-5_10.SHLVL
00000010 3D 31 00 5F 49 4E 49 54-5F 55 54 53 5F 4D 41 43 =1._INIT_UTS_MAC
00000020 48 49 4E 45 3D 73 75 6E-34 75 00 53 48 45 4C 4C HINE=sun4u.SHELL
00000030 3D 2F 62 69 6E 2F 62 61-73 68 00 48 4F 53 54 54 =/bin/bash.HOSTT
00000040 59 50 45 3D 73 70 61 72-63 00 4F 53 54 59 50 45 YPE=sparc.OSTYPE
00000050 3D 73 6F 6C 61 72 69 73-32 2E 38 00 48 4F 4D 45 =solaris2.8.HOME
00000060 3D 2F 65 78 70 6F 72 74-2F 68 6F 6D 65 2F 73 63 =/export/home/sc
00000070 7A 00 54 45 52 4D 3D 76-74 31 30 30 00 50 41 54 z.TERM=vt100.PAT
00000080 48 3D 2F 62 69 6E 3A 2F-75 73 72 2F 62 69 6E 3A H=/bin:/usr/bin:
00000090 2F 73 62 69 6E 3A 2F 75-73 72 2F 73 62 69 6E 3A /sbin:/usr/sbin:
000000A0 2F 75 73 72 2F 6C 6F 63-61 6C 2F 62 69 6E 3A 2E /usr/local/bin:.
000000B0 00 5F 49 4E 49 54 5F 4E-45 54 5F 53 54 52 41 54 ._INIT_NET_STRAT
000000C0 45 47 59 3D 6E 6F 6E 65-00 5F 3D 2F 62 69 6E 2F EGY=none._=/bin/
000000D0 6D 79 70 72 6F 67 5F 33-00 53 55 4E 57 2C 55 6C myprog_3.SUNW,Ul
000000E0 74 72 61 2D 35 5F 31 30-00 2F 75 73 72 2F 62 69 tra-5_10./usr/bi
000000F0 6E 2F 6D 79 70 72 6F 67-5F 33 00 00 00 00 00 00 n/myprog_3......
[scz@ /export/home/scz/src]>

前面说的getcwd()拼接技术同样适用于SPARC/Solaris 8。

与Linux相比,有几处重要变化,一是从0xC0000000变成0xFFBF0000,二是如果程序
在当前目录下,没有了前面的"./",三是SPARC芯片的一些对齐特性导致我们要找的
位置可能比想像的要向内存低端移动一些字节。

后两点容易理解,那我们是如何确定0xC0000000、0xFFBF0000的呢。这个问题实际上
就是<<如何编程获取栈底地址>>所解决的,参看这篇QA。假设堆栈(stack)向低地址
方向增长,则所谓栈底指堆栈(stack)最高地址,下面是tt用程序找出来的

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

利用ELF文件加载进内存之后的布局获取绝对路径固然可行,但是带有很强的Hacking
味,并不可取,非万不得已不要使用这种技术。

Linux有"/proc/self/exe",Solaris有"/proc/self/auxv",这些都可以一用。当然
在Solaris系统中直接使用getexecname(3C)获取execve()第一形参最好,否则涉及在
用户空间读取U区的问题,也带有很强的Kernel Hacking味道。参看<<如何在命令行
上访问指定进程P、U两区,如何欺骗Solaris的ps>>、<<getexecname(3C)是怎么实现
的>>两篇QA。

D: law@水木清华

在main()一开始就取execve()第一形参,或者退而求其次取argv[0],然后解析$PATH
环境变量,自己确定哪个目录里含有进程静态文件映像。如果是取execve()第一形参,
这个办法可行。如果取argv[0],就面临着argv[0]不可信的问题。而前面的技术取的
都是execve()第一形参。

注意,如果取argv[0],就不能依赖getcwd()拼接,这点与取execve()第一形参不同。
很可能命令行上的"myprog_3"实际根据$PATH产生了"/usr/bin/myprog_3"(execve()
第一形参),argv[0](myprog_3)与getcwd()(/home/scz/src)无论如何也拼不出正确
的绝对路径。

A: scz <scz@nsfocus.com> 2002-09-13 20:45

这是一个x86/FreeBSD 4.5-RELEASE系统中利用proc获取绝对路径的例子

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -g -static -o myprog_4 myprog_4.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#define MAXBUFSIZE 1024

int main ( int argc, char * argv[] )
{
char proc[64];
char buf[ MAXBUFSIZE ];
int count;

sprintf( proc, "/proc/%d/file", ( unsigned int )getpid() );
count = readlink( proc, buf, MAXBUFSIZE );
if ( count < 0 || count >= MAXBUFSIZE )
{
printf( "Failed/n" );
return( EXIT_FAILURE );
}
buf[ count ] = '/0';
printf( "%s -> [%s]/n", proc, buf );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

[scz@ /home/scz/src]> echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:.
[scz@ /home/scz/src]> myprog_4
/proc/121/file -> [/usr/home/scz/src/myprog_4]
[scz@ /home/scz/src]> ./myprog_4
/proc/122/file -> [/usr/home/scz/src/myprog_4]
[scz@ /home/scz/src]> ../src/myprog_4
/proc/123/file -> [/usr/home/scz/src/myprog_4]
[scz@ /home/scz/src]>

与Linux系统中"/proc/<pid>/exe"一样,FreeBSD系统中"/proc/<pid>/file"直接给
出了最期待的结果,没有冗余信息。参看procfs(5)手册页。

手头系统有限,无法一一验证其它Unix系统。个人觉得从栈底向低端移动取execve()
第一形参的办法比较通用,可以写成可移植的函数,但还是不太推荐这种Hacking。

13.13 x86/Linux Kernel 2.4.7-10的ptrace(2)手册页

13.14 x86/Linux Kernel 2.4.7-10下如何欺骗ps

Q: Redhat Linux 7.2,一运行中的程序,源代码不可控,ps ax可以看到其启动命令
行,如何欺骗ps ax,使之显示其他指定内容。

D: dennis2@www.linuxforum.net

在*BSD系统中可以参看setproctitle(3)、kvm_getargv(3)。

A: jbtzhm <jbtzhm@nsfocus.com>

rpm -qif `which ps`查到procps-2.0.7-11.src.rpm,在如下链接上找到

http://rpmfind.net/linux/RPM/redhat....7-11.i386.html

ftp://ftp.redhat.com/pub/redhat/lin....0.7-11.src.rpm

从readproc.c的file2strvec()函数中可以看到"ps ax"在读取/proc/<pid>/cmdline,
这个返回值是变长的、不固定的、动态分配内存的。

# cat /proc/self/cmdline | od -Ax -tx1 -v -
000000 63 61 74 00 2f 70 72 6f 63 2f 73 65 6c 66 2f 63
000010 6d 64 6c 69 6e 65 00
000017
#

可以看出/proc/<pid>/cmdline对应着argv[]数组。参看documentation/filesystems
/proc.txt。

当读取/proc/<pid>/cmdline时,调用了fs/proc/base.c中的proc_pid_cmdline()函
数,task_struct -> mm_struct -> arg_start、arg_end。为了欺骗ps ax,我们需
要修改arg_start与arg_end之间的数据。

参看fs/binfmt_elf.c中的load_elf_binary()、creat_elf_tables()函数。实际上这
里arg_start就是main()函数的argv[0]。

如果源代码可控,可以在main()入口处修改argv[]数组。源代码不可控时,如果进程
不依赖argv[0],可以在execve()第二形参处伪造argv[0],但无法伪造argv[1]及其
后续形参。最麻烦的情况是,源代码不可控的一个已经运行中的进程,此时要利用其
它技术修改该进程的argv[]数组。

jbtzhm采用的技术是利用ptrace()系统调用在指定进程的栈区搜索定位argv[],然后
替换之,清零或伪造。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o linux_fakeps linux_fakeps.c
* gcc -DDEBUG -Wall -pipe -g -o linux_fakeps linux_fakeps.c
*
* only for x86/Linux Kernel 2.4.7-10
*
* the author is jbtzhm, and modified by scz
*/

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

/*
* 这是操作系统相关、架构相关的栈底地址,参看<<如何编程获取栈底地址>>
*/
#define STACKBOTTOM ((unsigned int)0xC0000000)
#define STACKSIZE 4096

#if DEBUG

static void outputBinary ( const unsigned char *byteArray, const size_t
byteArrayLen )
{
size_t offset, k, j, i;

fprintf( stderr, "byteArray [ %u bytes ] ----> /n", byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/*
* if ( isprint( (int)byteArray[i] ) )
*/
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
return;
} /* end of outputBinary */

#endif

/*
* 形参count以32-bit为单位
*/
static int ptrace_rw ( enum __ptrace_request request, pid_t pid,
void *addr, void *data, size_t count )
{
unsigned int word;
unsigned int *child = ( unsigned int * )addr;
unsigned int *parent = ( unsigned int * )data;
size_t c;

if ( request == PTRACE_PEEKTEXT || request == PTRACE_PEEKDATA )
{
for ( c = 0; c < count; c++ )
{
/*
* 某些request导致ptrace()无错的时候也返回-1,为了消除歧义,考
* 虑在ptrace()调用之前设置errno成0,调用完成后检查errno是否还
* 是0
*/
errno = 0;
word = ( unsigned int )ptrace( request, pid, child, NULL );
if ( errno != 0 )
{
perror( "ptrace error" );
return( EXIT_FAILURE );
}
*parent = word;
child++;
parent++;
}
}
else if ( request == PTRACE_POKETEXT || request == PTRACE_POKEDATA )
{
for ( c = 0; c < count; c++ )
{
errno = 0;
ptrace( request, pid, child, *parent );
if ( errno != 0 )
{
perror( "ptrace error" );
return( EXIT_FAILURE );
}
child++;
parent++;
}
}
else
{
fprintf( stderr, "request error/n" );
return( EXIT_FAILURE );
}
return( EXIT_SUCCESS );
} /* end of ptrace_rw */

int main ( int argc, char * argv[] )
{
pid_t pid;
char *fake_string = NULL;
char buf[ STACKSIZE ];
char stack[ STACKSIZE ];
ssize_t len;
int fd;
long int ret;
char *target_argv;
int status;

if ( argc != 2 && argc != 3 )
{
fprintf( stderr, "Usage: %s <pid> [<fake string>]/n", argv[0] );
return( EXIT_FAILURE );
}
pid = ( pid_t )strtoul( argv[1], NULL, 10 );
if ( argc == 3 )
{
fake_string = argv[2];
}
memset( buf, 0, sizeof( buf ) );
snprintf( buf, sizeof( buf ), "/proc/%u/cmdline", ( unsigned int )pid );
if ( ( fd = open( buf, O_RDONLY ) ) < 0 )
{
perror( "open error" );
return( EXIT_FAILURE );
}
len = read( fd, buf, sizeof( buf ) );
close( fd );
if ( len <= 0 )
{
fprintf( stderr, "read error/n" );
return( EXIT_FAILURE );
}
else if ( len == sizeof( buf ) )
{
/*
* 此时我们无法判断原argv[]到底占用了多少字节
*/
fprintf( stderr, "We suggest to do nothing./n" );
return( EXIT_FAILURE );
}
#if DEBUG
outputBinary( ( const unsigned char * )buf, ( const size_t )len );
#endif
/*
* 关联指定进程,pid对应之进程即将中止运行
*/
if ( ( ret = ptrace( PTRACE_ATTACH, pid, NULL, NULL ) ) < 0 )
{
perror( "ptrace error" );
return( EXIT_FAILURE );
}
/*
* 注意,上面这个ptrace()返回时,pid对应的进程并不一定中止运行了,应该
* 调用waitpid()等待其中止运行
*/
if ( waitpid( pid, &status, WUNTRACED ) != pid )
{
perror( "waitpid error" );
return( EXIT_FAILURE );
}

/*
* 下面这部分代码纯粹是为了演示正规、严谨的ptrace()编程,并非绝对必要
*/
if ( WIFSTOPPED( status ) )
{
if ( WSTOPSIG( status ) != SIGSTOP )
{
fprintf( stderr, "The child had been stopped for another reason./n" );
return( EXIT_FAILURE );
}
}
else
{
fprintf( stderr, "Some error occur./n" );
return( EXIT_FAILURE );
}

if ( ptrace_rw( PTRACE_PEEKTEXT, pid, ( void * )( STACKBOTTOM - STACKSIZE ),
stack,
STACKSIZE / sizeof( unsigned int ) ) == EXIT_FAILURE )
{
return( EXIT_FAILURE );
}
target_argv = ( char * )memmem( stack, sizeof( stack ), buf, len );
if ( target_argv != NULL )
{
memset( target_argv, 0, len );
if ( fake_string != NULL )
{
if ( strlen( fake_string ) >= len )
{
fprintf( stderr, "Checking your [<fake_string>]./n" );
return( EXIT_FAILURE );
}
strcpy( target_argv, fake_string );
}
if ( ptrace_rw( PTRACE_POKEDATA, pid, ( void * )( STACKBOTTOM -
STACKSIZE ),
stack,
STACKSIZE / sizeof( unsigned int ) ) == EXIT_FAILURE )
{
return( EXIT_FAILURE );
}
}
else
{
fprintf( stderr, "We cann't find target_argv./n" );
}
errno = 0;
/*
* 去除对指定进程的关联,则pid对应之进程继续正常运行
*/
ptrace( PTRACE_DETACH, pid, NULL, NULL );
if ( errno != 0 )
{
perror( "ptrace error" );
return( EXIT_FAILURE );
}
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

linux_fakeps.c做了一定的保护性检查,避免系统崩溃,于是程序逻辑上有一个小问
题,如果对argv[]做了清零操作,则下次无法伪造成其它字符串。

Q: 运行linux_fakeps之后,ps ax的显示已经如期而变,可直接ps看到的依旧未变

A: jbtzhm <jbtzhm@nsfocus.com>

ps -p <pid> -o cmd
ps -p <pid> -o command
ps ax

对于这三种命令,linux_fakeps均有效。但是对于下面这三种命令,linux_fakeps无
效:

ps -p <pid> -o comm
ps axc
ps

那是因为这三种情况下使用了进程P区的comm[16],不再是argv[]。

readproc() -> file2str()、stat2proc() -> show_a_proc() -> show_cmd_env()

file2str()读取/proc/<pid>/stat,最终就是读取了指定进程P区的comm[16]。
stat2proc()负责解析来自/proc/<pid>/stat的数据。借助如下命令加强理解:

# cat /proc/self/stat | od -Ax -tx1 -v -

现在从内核源码中进一步分析这个问题。当读取/proc/<pid>/stat时,调用了
fs/proc/array.c中的proc_pid_stat()函数,最终读取了task->comm。为了欺骗
ps axc,我们需要修改task->comm,这是一个16字节的字符数组。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o linux_fakeps_1 linux_fakeps_1.c
* gcc -DDEBUG -Wall -pipe -g -o linux_fakeps_1 linux_fakeps_1.c
*
* only for x86/Linux Kernel 2.4.7-10
*
* the author is jbtzhm
*/

#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__

#include <stdio.h>

#define OFFSETOF(TYPE, MEMBER) ((size_t)&((TYPE)0)->MEMBER)
#define EXIT_FAILURE 1 /* Failing exit status */
#define EXIT_SUCCESS 0 /* Successful exit status */

/*
* 由于包含了<linux/sched.h>,无法正常包含这些函数原型所在头文件
*/
extern int close ( int fd );
extern off_t lseek ( int fildes, off_t offset, int whence );
extern int open ( const char *pathname, int flags );
extern ssize_t read ( int fd, void *buf, size_t count );
extern unsigned long int strtoul ( const char *nptr, char **endptr, int base );
extern ssize_t write ( int fd, const void *buf, size_t count );

#if DEBUG

static void outputBinary ( const unsigned char *byteArray, const size_t
byteArrayLen )
{
size_t offset, k, j, i;

fprintf( stderr, "byteArray [ %u bytes ] ----> /n", byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/*
* if ( isprint( (int)byteArray[i] ) )
*/
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
return;
} /* end of outputBinary */

#endif

/*
* ksyms -a | grep init_task_union
* cat /proc/ksyms | od -Ax -tx1 -v -
*/
static unsigned int resolve_symbol ( const char *symbol )
{
FILE *fd;
char buf[1024];
char *p;
unsigned int addr;

if ( ( fd = fopen( "/proc/ksyms", "r" ) ) == NULL )
{
perror( "fopen error" );
return( 0 );
}
memset( buf, 0, sizeof( buf ) );
/*
* reads in at most one less than size characters from stream
*/
while ( fgets( buf, sizeof( buf ), fd ) != NULL )
{
if ( ( p = strchr( buf, ' ' ) ) == NULL )
{
continue;
}
if ( strstr( p + 1, symbol ) != NULL )
{
#ifdef DEBUG
outputBinary( ( const unsigned char * )buf, ( const size_t
)strlen( buf ) );
#endif
*p = 0;
addr = ( unsigned int )strtoul( buf, NULL, 16 );
fclose( fd );
return( addr );
}
} /* end of while */
if ( feof( fd ) == 0 )
{
fprintf( stderr, "fgets error/n" );
}
fclose( fd );
return( 0 );
} /* end of resolve_symbol */

static void fake_task_comm ( pid_t pid, char *fake_string )
{
int kmem_fd;
struct task_struct task;
struct task_struct *init_task_union = ( struct task_struct *
)resolve_symbol( "init_task_union" );
struct task_struct *next_task;
char comm[16];

if ( init_task_union == NULL )
{
return;
}
if ( ( kmem_fd = open( "/dev/kmem", O_RDWR ) ) < 0 )
{
perror( "open error" );
return;
}
next_task = init_task_union;
/*
* 遍历进程链表,由于在用户空间读取内核动态数据,这个过程并不可靠
*/
while ( next_task != NULL )
{
lseek( kmem_fd, ( off_t )next_task, SEEK_SET );
if ( read( kmem_fd, &task, sizeof( task ) ) != sizeof( task ) )
{
fprintf( stderr, "read error./n" );
goto fake_task_comm_0;
}
if ( task.pid == pid )
{
memset( comm, 0, sizeof( comm ) );
if ( fake_string != NULL )
{
if ( strlen( fake_string ) >= sizeof( comm ) )
{
fprintf( stderr, "Checking your [<fake_string>]./n" );
goto fake_task_comm_0;
}
strcpy( comm, fake_string );
}
lseek( kmem_fd, ( off_t )next_task + OFFSETOF( struct task_struct
*, comm ), SEEK_SET );
if ( write( kmem_fd, comm, sizeof( comm ) ) != sizeof( comm ) )
{
fprintf( stderr, "write error./n" );
}
goto fake_task_comm_0;
}
next_task = task.next_task;
if ( next_task == init_task_union )
{
/*
* 双向循环链表
*/
break;
}
} /* end of while */

fake_task_comm_0:

close( kmem_fd );
return;
} /* end of fake_task_comm */

int main ( int argc, char * argv[] )
{
pid_t pid;
char *fake_string = NULL;

if ( argc != 2 && argc != 3 )
{
fprintf( stderr, "Usage: %s <pid> [<fake string>]/n", argv[0] );
return( EXIT_FAILURE );
}
pid = ( pid_t )strtoul( argv[1], NULL, 10 );
if ( argc == 3 )
{
fake_string = argv[2];
}
fake_task_comm( pid, fake_string );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

linux_fakeps_1.c首先利用/proc/ksyms获取struct task_struct结构链表头,这是
一个双向循环链表。由于获取的链表头地址是内核空间的地址,无法直接在用户空间
使用,于是利用/dev/kmem对其进行读写操作。

struct task_struct结构链表是内核空间的动态数据,linux_fakeps_1.c作为用户空
间程序去操作这个链表,缺乏足够的保护措施,并不可靠。好在我们所期待的效果也
不是常规需要,可以忽略这种不可靠。

D: 小四 <scz@nsfocus.com> 2002-09-25 0:00

我们可以利用linux_fakeps_1.c中演示的技术精确定位arg_start、arg_end,而不是
在栈区搜索定位argv[]。

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o linux_fakeps_2 linux_fakeps_2.c
* gcc -DDEBUG -Wall -pipe -g -o linux_fakeps_2 linux_fakeps_2.c
*
* only for x86/Linux Kernel 2.4.7-10
*/

/*
* 与linux_fakeps.c、linux_fakeps_1.c相比,linux_fakeps_2.c并未做欺骗ps的
* 操作,这里只是演示如何精确定位arg_start、arg_end
*/

#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__

#include <stdio.h>

#define EXIT_FAILURE 1 /* Failing exit status */
#define EXIT_SUCCESS 0 /* Successful exit status */

/*
* 由于包含了<linux/sched.h>,无法正常包含这些函数原型所在头文件
*/
extern int close ( int fd );
extern off_t lseek ( int fildes, off_t offset, int whence );
extern int open ( const char *pathname, int flags );
extern ssize_t read ( int fd, void *buf, size_t count );
extern unsigned long int strtoul ( const char *nptr, char **endptr, int base );
extern ssize_t write ( int fd, const void *buf, size_t count );

static void outputBinary ( const unsigned char *byteArray, const size_t
byteArrayLen )
{
size_t offset, k, j, i;

fprintf( stderr, "byteArray [ %u bytes ] ----> /n", byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/*
* if ( isprint( (int)byteArray[i] ) )
*/
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "/n" );
return;
} /* end of outputBinary */

/*
* ksyms -a | grep init_task_union
* cat /proc/ksyms | od -Ax -tx1 -v -
*/
static unsigned int resolve_symbol ( const char *symbol )
{
FILE *fd;
char buf[1024];
char *p;
unsigned int addr;

if ( ( fd = fopen( "/proc/ksyms", "r" ) ) == NULL )
{
perror( "fopen error" );
return( 0 );
}
memset( buf, 0, sizeof( buf ) );
/*
* reads in at most one less than size characters from stream
*/
while ( fgets( buf, sizeof( buf ), fd ) != NULL )
{
if ( ( p = strchr( buf, ' ' ) ) == NULL )
{
continue;
}
if ( strstr( p + 1, symbol ) != NULL )
{
#ifdef DEBUG
outputBinary( ( const unsigned char * )buf, ( const size_t
)strlen( buf ) );
#endif
*p = 0;
addr = ( unsigned int )strtoul( buf, NULL, 16 );
fclose( fd );
return( addr );
}
} /* end of while */
if ( feof( fd ) == 0 )
{
fprintf( stderr, "fgets error/n" );
}
fclose( fd );
return( 0 );
} /* end of resolve_symbol */

static void get_task_mm_arg ( pid_t pid )
{
int kmem_fd;
struct task_struct task;
struct task_struct *init_task_union = ( struct task_struct *
)resolve_symbol( "init_task_union" );
struct task_struct *next_task;
struct mm_struct mm;

if ( init_task_union == NULL )
{
return;
}
if ( ( kmem_fd = open( "/dev/kmem", O_RDWR ) ) < 0 )
{
perror( "open error" );
return;
}
next_task = init_task_union;
/*
* 遍历进程链表,由于在用户空间读取内核动态数据,这个过程并不可靠
*/
while ( next_task != NULL )
{
lseek( kmem_fd, ( off_t )next_task, SEEK_SET );
if ( read( kmem_fd, &task, sizeof( task ) ) != sizeof( task ) )
{
fprintf( stderr, "read task error./n" );
goto get_task_mm_arg_0;
}
if ( task.pid == pid )
{
lseek( kmem_fd, ( off_t )( task.mm ), SEEK_SET );
if ( read( kmem_fd, &mm, sizeof( mm ) ) != sizeof( mm ) )
{
fprintf( stderr, "read mm error./n" );
goto get_task_mm_arg_0;
}
outputBinary( ( const unsigned char * )( &mm.arg_start ), 16 );
goto get_task_mm_arg_0;
}
next_task = task.next_task;
if ( next_task == init_task_union )
{
/*
* 双向循环链表
*/
break;
}
} /* end of while */

get_task_mm_arg_0:

close( kmem_fd );
return;
} /* end of get_task_mm_arg */

int main ( int argc, char * argv[] )
{
pid_t pid;

if ( argc != 2 )
{
fprintf( stderr, "Usage: %s <pid>/n", argv[0] );
return( EXIT_FAILURE );
}
pid = ( pid_t )strtoul( argv[1], NULL, 10 );
get_task_mm_arg( pid );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

14. 一些小工具的使用

14.0

14.1 如何在命令行上进行8进制、10进制、16进制之间的转换

14.2 显示文件的三个时间戳(atime、mtime、ctime)

A: Sun Microsystems 2001-04-05

首先理解如下概念

mtime - time the contents of file was last modified (written to).
atime - time the file was last used/accessed (read or executed)
ctime - time the inode of the file was last changed (like changing permissions).
ctime also gets updated every time mtime is modified, but not when the
atime is changed.

ctime, is NOT the files creation date. Creation date is not recorded
anywhere in the Unix file system.

没有一个现成的Unix命令直接达到这个目的,但很容易利用truss命令获取这三个时
间戳

$ ls -l /usr/bin/ls
-r-xr-xr-x 1 root bin 17440 1997 7月 15 /usr/bin/ls*
$ ls -lc /usr/bin/ls
-r-xr-xr-x 1 root bin 17440 2月 5 21:27 /usr/bin/ls*
$ ls -lu /usr/bin/ls
-r-xr-xr-x 1 root bin 17440 6月 19 22:18 /usr/bin/ls*
$ truss -vlstat -tlstat ls /usr/bin/ls
lstat64("/usr/bin/ls", 0xEFFFFC18) = 0
d=0x025C0006 i=48376 m=0100555 l=1 u=0 g=2 sz=17440
at = Jun 19 22:18:14 GMT 2001 [ 993017894 ]
mt = Jul 15 20:22:33 GMT 1997 [ 869026953 ]
ct = Feb 5 21:27:42 GMT 2001 [ 981437262 ]
bsz=8192 blks=36 fs=ufs
/usr/bin/ls
$

参看stat(2)手册页了解更多信息。时间戳记录在inode中,单位是秒。这是ufs文件
系统的特性,和32-bit/64-bit kernel mode无关。如果几个文件在一秒内创建,则
它们的时间戳完全一致,此时"ls -t"根据文件名排序。

D: scz <scz@nsfocus.com>

在SPARC/Solaris 7下参看ls(1)手册页

/usr/bin/ls -lu 对应atime
/usr/bin/ls -lc 对应ctime

-u time of last access
-c time of last modification of the i-node (file created, mode changed)

既不指定-u也不指定-c的时候,对应mtime。

此外,SPARC/Solaris没有x86/Linux所提供的stat命令,为了获取stat信息,可以使
用"truss -vlstat -tlstat ls <filename>"命令。

14.3 只在本地文件系统上查找

14.5 反汇编

15. 32-bit/64-bit相关问题

15.1 Solaris下如何识别当前内核版本

Q: 我编写了一个内核模块,在Solaris 7/8下编译通过,模块中如何识别当前正在运
行内核版本

A: Andrew Gabriel

参看adb使用的宏"utsname",32-bit的位于usr/lib/adb/utsname,此外可以参看
/usr/include/sys/utsname.h文件,但是这些不是DDI/DKI兼容的。

sysdef | more 看看

A: scz <scz@nsfocus.com>

非DDI/DKI兼容意味着丧失良好的可移植性,未来版本的Solaris可能不再提供相应的
支持,但是就Kernel Hacking而言是可以一试的。

adb -P 'sun>' -k /dev/ksyms /dev/mem

sun>$</usr/lib/adb/sparcv9/utsname <-- 使用64-bit宏

utsname:
utsname: sys SunOS
utsname+0x101: node sun27
utsname+0x202: release 5.7
utsname+0x303: version Generic_106541-08
utsname+0x404: machine sun4u
sun>$q

cat /usr/lib/adb/sparcv9/utsname

utsname+0="" <-- 指定当前地址.为utsname
+/"sys"8t257c <-- t表示tab,c表示以字符方式显示1个字节
+/"node"8t257c
+/"release"8t257c <-- +表示当前地址.递增
+/"version"8t257c
+/"machine"8t257c

more /usr/include/sys/utsname.h

#define _SYS_NMLN 257 /*
* 4.0 size of utsname elements
* Must be at least 257 to
* support Internet hostnames.
*/
#define SYS_NMLN _SYS_NMLN

struct utsname
{
char sysname[_SYS_NMLN];
char nodename[_SYS_NMLN];
char release[_SYS_NMLN];
char version[_SYS_NMLN];
char machine[_SYS_NMLN];
};

显然adb下的命令是针对struct utsname结构来的。简单地truss uname -a,可以看
到如下输出

ioctl(1, TCGETA, 0xFFBEE5DC) = 0
sysinfo(SI_ARCHITECTURE, "sparc", 257) = 6
sysinfo(SI_PLATFORM, "SUNW,Ultra-5_10", 257) = 16

暂时没有跟踪这几个系统调用在做什么,想必类似/usr/lib/adb/sparcv9/utsname宏。

15.2 如何启动Solaris 32-bit/64-bit内核

Q: Algos@Unix 水木清华 2001-12-04 18:12

对于UltraSPARC-I/Solaris 8,可以修改/platform/sun4u/boot.conf文件,使操作
系统运行在64位或者32位,但对于其他机型呢,比如E420、E450、E4500之类服务器
怎么改?好像起来后就是64位的,怎么才能改成32位的呢?

Q: 显然有一些32-bit驱动程序以及一些应用软件不能工作在64-bit内核下,比如gcc
编译的32-bit IP Filter。我必须启动到32-bit内核模式下,怎么办?

A: dkoleary@mediaone.net

32-bit : ok boot disk kernel/unix
64-bit : ok boot disk kernel/sparcv9/unix

为了设置成缺省启动内核模式

32-bit : ok setenv boot-file kernel/unix
64-bit : ok setenv boot-file kernel/sparcv9/unix

为了确定你所启动的内核模式

isainfo -b

根据你所启动的内核模式,该命令分别返回32、64

A: Will Wang <willcyw@kimo.com.tw> 2001-06-09 02:17

一个办法就是启动时按Stop-A进入OK模式,输入

ok> setenv boot-file kernel/unix
ok> boot

另一个办法是已经在shell状态下了,执行命令

# eeprom "boot-file=kernel/unix"

系统重启之后将自动加载32-bit内核

15.3 gcc支持64-bit编译吗

Q: gcc -v显示版本2.95.2,isainfo -kv显示64-bit sparcv9 kernel modules,我
企图通过指定"-mcpu=v9 -m64"获得64-bit代码,提示m64未被支持,仅仅指定
mcpu=v9,在汇编阶段报告""v8 can't generate v9 code",我使用的汇编器是
/usr/ccs/bin/as,随Solaris 7/8提供的。

这是什么问题,我需要一个64-bit汇编器吗,从哪里获取呢?

A: Robert Banniza <robert@rootprompt.net>

我并不认为gcc 2.95.2已经开始支持64-bit编译模式,或许你应该考虑采用
Sun WorkShop Compiler SPARC 5.0/6.0。

15.4 Solaris启动时内核文件找不到了

Q: 我的Solaris 7莫名其妙死机了,只好关电源,再开,发现错误
boot with command:boot now
cann't open now
enter filename[now]:
怎么办

A: dkoleary@mediaone.net

启动时按Stop-A进入ok状态,在这里输入

32-bit : ok boot disk kernel/unix
64-bit : ok boot disk kernel/sparcv9/unix

为了设置成缺省启动内核模式

32-bit : ok setenv boot-file kernel/unix
64-bit : ok setenv boot-file kernel/sparcv9/unix

A: liqun.bbs@bbs.gznet.edu.cn

试试这个,启动时按Stop-A进入OK状态

OK> setenv boot-file kernel/unix
OK> reset

15.5 64-bit驱动程序无法在8下关联,但在7下工作正常

Q: 一个64-bit驱动程序在Solaris 7下加载、关联(load & attach)成功,但在8下加
载(load)成功、关联(attach)失败。

A: Sun Microsystems 1998-06-13

从Solaris 8开始,64-bit驱动程序必须位于"sparcv9/"目录中。而在Solaris 7中,
尽管不提倡,但即使64-bit驱动程序不在"sparcv9/"目录中,也可以加载并关联成功。

16. 库相关问题

16.0 为什么用高版glibc编译生成的程序不能与低版glibc搭配运行

A: jbtzhm <jbtzhm@nsfocus.com> 2003-01-16 16:49

gcc生成程序时会添加sh_type为SHT_GNU_verdef的节,包含了glibc版本信息,以此
防止该程序与低版glibc搭配运行。

16.1 在Solaris 7下编写网络程序需要链接哪些库

Q: inet_pton()是什么库里的,为什么man手册里无对应内容

A: scz <scz@nsfocus.com>

这个函数比较新,还有另外几个,比如inet_ntop()。关于它们的详细介绍参看
<<Unix Network Programming>> 3.7 小节。文件/usr/include/arpa/inet.h中定义
有:

extern int inet_pton ( int, const char *, void * );

用/usr/ccs/bin/nm工具观察三个动态链接库libresolv.so、libsocket.so、
libnsl.so提供的全局函数

$ nm -g /usr/lib/libresolv.so | grep "|FUNC |GLOB |" | grep -v "|UNDEF |"
... ...
[1043] | 25104| 632|FUNC |GLOB |0 |12 |inet_aton
[1088] | 24220| 80|FUNC |GLOB |0 |12 |inet_ntop
[1061] | 25928| 72|FUNC |GLOB |0 |12 |inet_pton
... ...
$ nm -g /usr/lib/libsocket.so | grep "|FUNC |GLOB |" | grep -v "|UNDEF |"
... ...
[314] | 31056| 16|FUNC |GLOB |0 |11 |_accept
[343] | 31024| 16|FUNC |GLOB |0 |11 |_bind
[310] | 31072| 16|FUNC |GLOB |0 |11 |_connect
[387] | 31224| 16|FUNC |GLOB |0 |11 |_getpeername
[377] | 31240| 16|FUNC |GLOB |0 |11 |_getsockname
[233] | 31256| 16|FUNC |GLOB |0 |11 |_getsockopt
[276] | 31040| 16|FUNC |GLOB |0 |11 |_listen
[290] | 31104| 20|FUNC |GLOB |0 |11 |_recv
[315] | 31124| 20|FUNC |GLOB |0 |11 |_recvfrom
[200] | 31144| 20|FUNC |GLOB |0 |11 |_recvmsg
[273] | 27992| 360|FUNC |GLOB |0 |11 |_ruserpass
[340] | 31164| 20|FUNC |GLOB |0 |11 |_send
[399] | 31184| 20|FUNC |GLOB |0 |11 |_sendmsg
[207] | 31204| 20|FUNC |GLOB |0 |11 |_sendto
[371] | 31272| 16|FUNC |GLOB |0 |11 |_setsockopt
[312] | 31088| 16|FUNC |GLOB |0 |11 |_shutdown
[272] | 30320| 16|FUNC |GLOB |0 |11 |_socket
[385] | 16820| 96|FUNC |GLOB |0 |11 |getnetbyaddr
[334] | 16724| 96|FUNC |GLOB |0 |11 |getnetbyname
[212] | 18212| 96|FUNC |GLOB |0 |11 |getprotobyname
[282] | 19716| 96|FUNC |GLOB |0 |11 |getservbyname
[388] | 19812| 96|FUNC |GLOB |0 |11 |getservbyport
[373] | 15152| 8|FUNC |GLOB |0 |11 |htonl
[375] | 15160| 12|FUNC |GLOB |0 |11 |htons
[289] | 15172| 8|FUNC |GLOB |0 |11 |ntohl
[293] | 15180| 12|FUNC |GLOB |0 |11 |ntohs
... ...
$ nm -g /usr/lib/libnsl.so | grep "|FUNC |GLOB |" | grep -v "|UNDEF |"
... ...
[3386] | 119424| 16|FUNC |GLOB |0 |12 |clnt_create
[3650] | 217312| 120|FUNC |GLOB |0 |12 |gethostbyaddr
[3391] | 217216| 96|FUNC |GLOB |0 |12 |gethostbyname
[4105] | 217432| 96|FUNC |GLOB |0 |12 |gethostent
[4071] | 170896| 52|FUNC |GLOB |0 |12 |gethostname
[3759] | 123100| 648|FUNC |GLOB |0 |12 |inet_addr
[3857] | 272664| 244|FUNC |GLOB |0 |12 |inet_ntoa
... ...
$

显然,如果涉及RPC编程,必然需要libnsl.so,而inet_pton()来自libresolv.so。
总结一下,实在不能确定的时候,编译时指定链接开关如下:

-lsocket -lnsl -lresolv

16.2 SUID设置和LD_LIBRARY_PATH环境变量

Q: RedHat Linux 6.1/6.2,C编程,还有一些脚本

execl()以及其他exec...()执行一个SUID程序的时候,出于安全考虑,会清除
LD_LIBRARY_PATH环境变量,仅仅依靠系统全局设置搜索共享库。参看如下URL

http://spdoc.pdc.kth.se/doc_link/C/...setrf1/exec.htm

现在有一个程序,需要一个正确的LD_LIBRARY_PATH环境变量设置才能运行,可是
由于某些原因必须做SUID设置,结果最终运行失败。我尝试在程序中putenv()、
setenv(),失败,显然LD_LIBRARY_PATH环境变量需要在程序加载过程中由动态链
接器使用,程序中的putenv()、setenv()为时已晚。

于是我写了一个脚本,在脚本中设置LD_LIBRARY_PATH环境变量,调用C程序,对
脚本做SUID设置。但是脚本的SUID设置并没有传递给子进程(这里就是那个C程序)

A: Paul Sack <paul-sackun@jefe.eyep.net>

到www.google.com用"suid shell scripts race conditions"进行搜索,查看
BugTraq相关讨论。安全的解决办法是用C写一个SUID WRAPPER去exec...()你的C程序,
在SUID WRAPPER中设置LD_LIBRARY_PATH环境变量。下面这个举例未经测试

--------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

const char * env_init[] = { "LD_LIBRARY_PATH=/your/path/", NULL };

int main ( void )
{
execle( "/full/path/to/program", "program", "arg1",
"arg2", ... , NULL, env_init );
perror( "Error in execle" ); /* execle did not work */
exit( 1 );
}
--------------------------------------------------------------------------

参看Stevens的APUE(Unix环境高级编程)Page 211,我高度推荐你研究该书。记得给
这个wrapper加suid设置。

A: Hemant Shah <shah@typhoon.xnet.com>

我碰到过同样的问题,当时的解决办法是编辑/etc/ld.so.conf文件,增加一条共享
搜索路径,最后运行ldconfig命令。执行一个命令(无论是否SUID)时,动态链接器检
查由ldconfig命令产生的cache文件(/etc/ld.so.cache),根据其中的路径设置搜索
共享库,比如

$ cat /etc/ld.so.conf
/usr/X11R6/lib
/usr/lib
/usr/i486-linux-libc5/lib
/usr/lib/qt-2.0.1/lib
/usr/lib/qt-1.44/lib
/opt/MfCobol_V4.1_8d.H3.13.03/coblib
$

最后一条路径是我增加的,然后运行ldconfig命令。

A: Unkown

但是这种解决办法只能工作在Linux上,并不适用其他ELF系统,比如Solaris。对于
这些系统,可以在链接器命令行上指定额外的共享库搜索路径,或者在编译时设置
LD_RUN_PATH环境变量

D: BugTraq MailList

考虑下面的程序

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o suid suid.c
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main ( void )
{
printf( "uid[%d] euid[%d] gid[%d] egid[%d]/n",
getuid(), geteuid(), getgid(), getegid() );
return( 0 );
}
--------------------------------------------------------------------------

# chown root suid
# chmod u+s suid
$ ./suid
uid[505] euid[0] gid[100] egid[100]

编辑一个如下脚本suid.sh

--------------------------------------------------------------------------
#! /home/scz/src/suid
--------------------------------------------------------------------------

$ ./suid.sh
uid[505] euid[0] gid[100] egid[100]

在RedHat 6 2.2.12-20上测试的。但是这样有什么意义呢?不懂

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

如果一个程序是SUID过的,将导致LD_LIBRARY_PATH环境变量被忽略,但是这不是问
题本质所在,本质原因在于ruid不等于euid(或者rgid不等于egid)。所以wrapper中
仅仅重置环境变量是不够的,必须想办法修改ruid等于euid。最好还是重新编译程序,
使之不依赖于LD_LIBRARY_PATH环境变量。

16.3 链接过程中库的顺序

Q: 有几个库文件A.a、B.a、common.a,前两者用到了定义在后者中的例程,如果把
common.a放在前面,链接器报告存在无法解析的符号名,放在最后则无问题。

A: Floyd Davidson <floyd@ptialaska.net>

链接器按照命令行上指定顺序搜索库文件和目标文件(.a .o),二者之间的区别在
于.o文件被全部链接进来,而只从库文件中析取所需模块,仅当某个模块可以解
析当前尚未成功解析的符号时,该模块被析取后链接进来。如果库文件无法解析
任何当前尚未成功解析的符号,不从中析取也不发生链接。

Unix编程新手的常见问题是数学函数并不在标准C库中,而是在libm.a中

cc -lm foo.c

这里foo.c用到了数学库中的符号,但是链接器无法正确解析。当搜索到libm.a时,
来自foo.c的数学函数符号尚未出现,因此不需要析取libm.a的任何模块。接下来
foo.o链接进来,增加了一批尚未成功解析的符号,但已经没有libm.a可供使用了,
因此数学库必须在foo.o之后被搜索到。

cc foo.c -lm

在你的问题中,如果common.a首先被搜索到,因为不匹配尚未成功解析的符号,
而被丢弃。结果A.a和B.a真正链接进来的时候,已经没有库可以解析符号了。

D: 注意这里说的是静态库,对于动态库则有所不同。如果指定-static静态编译,就
要特别注意这个库的顺序问题。

16.4 Solaris 2.x下如何构造动态链接库

16.5 如何生成linux下的共享库

16.6 /usr/lib/ld.so.1损坏或丢失

Q: 意外地覆盖了ld.so.1,幸运的是有一个原始备份,可我没有一个静态链接版本的
命令去恢复它。

Q: 我在Solaris 2.6中做了"mv /usr/lib /usr/lib1",本意是想使用自己的库,但
是现在所有程序都报告"找不到/usr/lib/ld.so.1",怎么办

A: scz <scz@nsfocus.com>

不要重启动,立即用/usr/sbin/static/mv、/usr/sbin/static/cp命令恢复

# ls /usr/sbin/static
cp* ln* mv* rcp* tar*
#

Q: 那如果此时/usr被改名了,怎么办?

A: faint,谁这么变态。假设/usr改名成了/faint,

1) /faint/sbin/static/cp /faint/sbin/static/mv /tmp/mv
2) /tmp/mv /faint /usr

我不确定

1) /faint/sbin/static/mv /faint /usr

能否成功,你可以自己测试一下效果。或者

ok boot cdrom -s (放入启动安装光盘)
mount -F ufs /dev/dsk/c0t0d0s0 /mnt (这里指定原根文件系统对应的设备名)
mv /mnt/faint /mnt/usr

D: cirrus@SMTH

建议把/usr/sbin/static下的东西拷一份到/sbin下或者其它比较可信的跟/在同一个
fs的目录下。装机器的时候,不管什么OS,/usr都是单独一个fs的。

16.7 Solaris下如何使用LD_PRELOAD环境变量

A: Sun Microsystems 1999-06-15

下面即将演示如何利用LD_PRELOAD环境变量影响标准I/O库函数printf(3S)。环境变
量LD_PRELOAD的值是whitespace-separated的共享库列表,运行时链接器负责解释它。
由LD_PRELOAD指定的共享库优于其他共享库加载。

--------------------------------------------------------------------------
/* main.c */
#include <stdio.h>
#include <stdlib.h>

int main ( int argc, char * argv[] )
{
char s[] = "Hello World/n";

printf( s );
return( EXIT_SUCCESS );
}
--------------------------------------------------------------------------

--------------------------------------------------------------------------
/* mylib.c */
#include <ctype.h>
#include <unistd.h>

int printf ( char * s )
{
char *t = s;

while ( *s )
{
*s = toupper( *s ), s++;
}
return( ( int )write( 0, t, strlen( t ) ) );
}
--------------------------------------------------------------------------

--------------------------------------------------------------------------
# Makefile

CC="/opt/SUNWspro/SC5.0/bin/cc"

all: a.out

mylib.so: mylib.c
<TAB> ${CC} -g -G -o mylib.so mylib.c

a.out: main.c mylib.so
<TAB> ${CC} -g main.c

clean:
<TAB> rm -rf mylib.so mylib.o a.out *~
--------------------------------------------------------------------------

[scz@ /export/home/scz/src]> sotruss ./a.out
a.out -> libc.so.1:*atexit(0xff3b9c6c, 0x20800, 0x0)
a.out -> libc.so.1:*atexit(0x109f0, 0xff3b9c6c, 0xff235e68)
a.out -> libc.so.1:*printf(0xffbefa47, 0xff239c1c, 0xff235e60)
Hello World
a.out -> libc.so.1:*exit(0x0, 0xffbefabc, 0xffbefac4)
[scz@ /export/home/scz/src]>

注意到来自动态链接库"libc.so.1"的库函数printf()在"a.out"中被调用。现在,如
果你想使用来自"mylib.so"的printf()函数,利用LD_PRELOAD环境变量通知运行时链
接器优先使用"mylib.so"解析未知符号。

[scz@ /export/home/scz/src]> LD_PRELOAD=./mylib.so ./a.out
HELLO WORLD
[scz@ /export/home/scz/src]>

为了解释更清楚些,再次使用sotruss(1)命令

[scz@ /export/home/scz/src]> LD_PRELOAD=./mylib.so sotruss ./a.out
a.out -> libc.so.1:*atexit(0xff3b9c6c, 0x20800, 0x0)
a.out -> libc.so.1:*atexit(0x109f0, 0xff3b9c6c, 0xff235e68)
a.out -> mylib.so:*printf(0xffbefa27, 0xff239c1c, 0xff235e60)
HELLO WORLD
a.out -> libc.so.1:*exit(0x0, 0xffbefa9c, 0xffbefaa4)
[scz@ /export/home/scz/src]>

如你所见,运行时链接器现在使用了来自动态链接库"mylib.so"的printf()函数。

16.8

16.9 Solaris 8下如何配置运行时链接环境

Q: 在Linux下我知道用ldconfig(8)配置运行时链接环境,但是在Solaris 8下呢

A: <cypher@punk.net>

你总是可以利用 LD_LIBRARY_PATH 环境变量,对于Solaris 8,还可以参看crle(1)
手册页。

A: Logan Shaw <logan@cs.utexas.edu>

如果在链接时使用了"-R"和"-L"选项,则相关动态库的路径将保存在ELF文件中,于
是以后的运行中不再需要设置环境变量去定位动态库。比如,有一个
/usr/local/lib/libfoo.so,而你的bar程序需要这个libfoo.so,编译、链接时最好
这样

gcc -Wall -pipe -O3 -o bar -R/usr/local/lib -L/usr/local/lib bar.c -lfoo

D: 对于FreeBSD/Linux,man ldconfig

16.10 libcrypto.so.0.9.6是什么软件包里的

16.11 共享库的动态加载/卸载

A: Mark Mitchell,Jeffrey Oldham,Alex Samuel 2002-08-07 22:43

这份文档来自<<Advanced Linux Programming>>,解释如何动态加载共享库,使用这
种技术你可以在程序中精确控制加载某个共享库。本文同时讨论了共享库中符号解析
问题。

dlopen( "libtest.so", RTLD_LAZY );

这个调用将打开共享库libtest.so,第二形参通常都是RTLD_LAZY。为了使用dlopen
函数,需要包含<dlfcn.h>头文件,指定链接开关-ldl。

假设libtest.so中定义了函数my_function

--------------------------------------------------------------------------
void *handle;
void ( *test ) ( void );

handle = dlopen( "libtest.so", RTLD_LAZY );
test = dlsym( handle, "my_function" );
test();
dlclose( handle );
--------------------------------------------------------------------------

dlsym()系统调用还可用于获取指向共享库中某个静态变量的指针。dlopen与dlsym调
用失败时均返回NULL,此时可以调用dlerror(没有形参)获取相应的可读错误信息。

dlclose函数用于卸载共享库。技术上,dlopen只在一个共享库尚未被加载的情况下
真正加载它。如果一个共享库已经被加载了,dlopen简单地递增该共享库引用计数。
类似的,dlclose递减共享库引用计数,当引用计数达到零时卸载共享库。

如果你是用C++编写共享库,而又想用dlsym访问其中的函数、静态变量,可能需要这
样定义:

--------------------------------------------------------------------------
#ifdef __cplusplus
extern "C"
{
#endif

static char my_char;
static void my_funtion ( void );

#ifdef __cplusplus
}
#endif
--------------------------------------------------------------------------

这使得C++编译器保持my_char、my_funtion的原始名字。对于C编译器不存在该问题。

一个共享库可能会引用外部定义的函数和变量。假设你用dlopen打开这样一个共享库。
如果指定了RTLD_LAZY,Linux不会在立即解析未定义的符号名,直到共享库中代码第
一次试图引用未定义的符号名,Linux才开始解析它。如果解析成功,程序继续执行,
反之显示错误信息并终止程序。如果指定了RTLD_NOW,Linux会在调用dlopen时立即
解析未定义的符号名。解析失败时dlopen返回NULL。

那么Linux根据什么解析未定义的符号名呢,有这么几种情况:

如果主程序引出(exports)任意动态符号(共享库正是这样做的),则这些符号自然可
用。然而缺省情况下,常规可执行程序不会以动态符号形式引出它们的函数和变量名。
为了达到这个效果,可以在编译时指定"-Wl,-export-dynamic",这实际是链接选项。

如果主程序编译时选择了动态链接,则dlopen打开的库可以引用编译时共享库中的符
号。

如果主程序使用dlopen打开共享库A、B,缺省情况下A与B彼此不能引用对方定义的动
态符号。但是可以在dlopen第二形参上逻辑或一个RTLD_GLOBAL标志,使得相应共享
库中动态符号全局可见。

--------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <dlfcn.h>

int main ( int argc, char * argv[] )
{
void *handle;
void ( *foo ) ( void );

handle = dlopen( "libfoo.so", RTLD_LAZY );
foo = dlsym( handle, "foo" );
foo();
dlclose( handle );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

假设foo()调用了另一个函数bar(),而bar()不是libfoo.so中定义的,怎么办。

一种办法是在你的主程序里包含bar()函数体,然后指定"-Wl,-export-dynamic"编译
主程序:

$ cc -Wl,-export-dynamic -o main main.c bar.c -ldl

第二种办法是将bar()放到单独一个共享库里,编译时动态链接进主程序

$ cc -fPIC -shared -o libbar.so bar.c
$ cc -o main main.c libbar.so -ldl

第三种办法是将bar()放到单独一个共享库里,运行时由main函数调用dlopen动态加
载。注意,dlopen的第二形参中必须逻辑或RTLD_GLOBAL标志,否则libfoo.so看不到
libbar.so引出的动态符号。

--------------------------------------------------------------------------
void *bar_handle;

bar_handle = dlopen( "libbar.so", RTLD_LAZY | RTLD_GLOBAL );
--------------------------------------------------------------------------

Q: 我试图用dlopen()打开一个共享库,该共享库使用了一个extern型变量,后者位
于主调二进制文件中。但是这个dlopen()调用失败了,报告存在无法解析的外部
符号。我用的是gcc(Cygnus version 2.9),怎么解决这个问题。

--------------------------------------------------------------------------
/*
* Library testlib.c
* gcc -Wall -pipe -O3 -o testlib.o -c testlib.c
* /usr/ccs/bin/ld -G testlib.o -o testlib.so
*/
extern int abc;

void testing ( void )
{
abc = 0;

return;
}

/*
* Main testcall.c
* gcc -Wall -pipe -O3 -o testcall testcall.c -ldl
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <dlfcn.h>

int abc = -1;

int main ( void )
{
void *testlib;
void ( *testing_call ) ();

fprintf( stderr, "abc = %d/n", abc );
if ( ( testlib = dlopen( "./testlib.so", RTLD_NOW | RTLD_GLOBAL ) ) != NULL )
{
testing_call = dlsym( testlib, "testing" );
( *testing_call )();
fprintf( stderr, "abc = %d/n", abc );
}
return( EXIT_SUCCESS );
}
--------------------------------------------------------------------------

A: Sun Microsystems 2001-04-11

Sun这份文档认为如果使用Sun Workshop编译、链接,将不存在前述问题。如果使用
gnu ld生成主调程序,应该使用--export-dynamic选项。

在SPARC/Solaris 7/8 64-bit kernel mode测试上述程序,并无问题,这可能是因为
并未使用gnu ld的缘故,此时使用的是/usr/ccs/bin/ld。当前gcc版本:

$ gcc -v
gcc version 2.95.2 19991024 (release)
$

A: flyriver 2001-12-16 22:48

Linux系统中编译主调程序时加上"-rdynamic"参数

Q: 在libfoo.so中实现了func1()、func2(),其中func2()调用了func1()。主调程序
prog.c中包含一个同名函数func1(),但与libfoo.so中func1()有不同形参类型。
prog.c使用dlopen()打开libfoo.so,并调用其中的func2()。我期望此时func2()
仍去调用libfoo.so中的func1(),但现在事实是调用了prog.c中的func1(),怎么
办。

A: Paul Pluzhnikov <ppluzhnikov@earthlink.net> 2003年5月31日 13:05

对于Win32、AIX来说,你所期待的效果是缺省行为,但其它操作系统未必如此。对于
Solaris、FreeBSD、Linux,使用gnu ld生成libfoo.so时应指定-Bsymbolic。缺省情
况下没有指定-Bsymbolic,此时prog.c中的func1()被func2()调用。

A: Mars Rullgard <mru@users.sourceforge.net> 2003年5月30日 17:09

如果libfoo.so没有使用prog.c中符号(变量、函数),使用gnu ld链接prog.c时不要
指定-rdynamic或者-export-dynamic,这样prog.c中func1符号不被引出,func2()就
不会调用func1()。如果还是不能解决问题,设法使func1这个符号成为weak symbol,
假设你正在使用gcc,可以这样做:

int __attribute__((weak)) func1 ( ... )
{
... ...
}

16.12 编译时命令行指定-ldl,ldd观察时却是libdl.so.2,为什么

A: jbtzhm <jbtzhm@nsfocus.com> 2003-01-16 16:49

ld命令在链接过程中填写ELF文件的DT_NEEDED域,也就是ldd命令所观察到的内容,
下面是ELF规范

--------------------------------------------------------------------------
* DT_NEEDED

该表项给出一个string table index,对应的串是所需要的库名字。这个string
table由DT_STRTAB表项确定。_DYNAMIC数组可能包含多个这样的表项,这种表项间的
相互顺序很重要。

* DT_SONAME

该表项给出一个string table index,对应的串是动态链接库名。这个string
table由DT_STRTAB表项确定。
--------------------------------------------------------------------------

ld在填写DT_NEEDED域时,首先采用动态库自身的DT_SONAME域,如果没有此域,则采
用动态库文件名。可以查看ELF文件的".dynamic section"获取DT_SONAME设置:

. 在SPARC/Solaris 8上执行"elfdump -d /lib/libdl.so | grep SONAME"

. 在x86/Linux上执行"objdump -x /lib/libdl.so | grep SONAME"

Q: 编译动态链接库时如何指定DT_SONAME

A: jbtzhm <jbtzhm@nsfocus.com> 2003-01-16 16:49

一般如此编译生成动态链接库

gcc -fpic -shared -O4 -pipe -s -o libfile.so file.c

此时没有指定DT_SONAME,如欲指定DT_SONAME,可用如下命令编译

gcc -c -fPIC file.c -O3 -o file.o
gcc -shared -Wl,-soname,libfile.so.1 -O3 -o libfile.so file.o

此时DT_SONAME被指定成libfile.so.1,ldd看到libfile.so.1,而不是libfile.so。

16.13 如何进行部分静态链接

Q: 在g++/gcc命令行上指定-static时,所有库都进行静态链接,但是现在我想部分
静态链接,比如:

statically lib_a.a
dynamically lib_b.so

A: Valentin Nechayev <netch@segfault.kiev.ua> 2003-03-12 10:45

如果使用GNU ld,可以这样试试:

gcc -o $prog $objs -Wl,-Bstatic -l_a -Wl,-Bdynamic -l_b -Wl,-Bdynamic

最后的-Wl,-Bdynamic表示将缺省库链接模式恢复成动态链接,比如libc的链接。如
果使用的不是GNU ld,请参看相应的man手册。

17. 文件查看问题

17.0 如何改变vi临时目录

Q: 缺省情况下vi临时目录是/var/tmp,可是现在由于某些原因该目录只读,我想使
用vi。

A: 一般修改$HOME/.exrc,在其中设置一行"set directory=/tmp"。如果$HOME也是
只读的,还可以export EXINIT="set directory=/tmp"。然后执行vi即可。

17.1 如何直接查看man文件

A: scz <scz@nsfocus.com>

下面几种方法都可以

/bin/nroff -man <your man doc> | more -s
groff -Tlatin1 -mandoc <your man doc> | less
groff -s -p -t -e -Tascii -mandoc <your man doc> | less

有less的时候建议使用less,而不是more。

在Linux下更简单,不用这样麻烦,比如

ls /usr/man/man5/nologin.5.gz
man /usr/man/man5/nologin.5.gz
man /usr/man/man1/finger6.1

无论什么系统,总是可以利用MANPATH环境变量的

$ mkdir man1
$ cp /usr/man/man1/proc.1 man1
$ man -s 1 -M . proc

17.2 .tex文件怎么读

A: shuoshu.bbs@bbs.whnet.edu.cn

用 latex *.tex 编译生成dvi文件,然后用 xdvi 看

17.3 Solaris下怎么看.ps文件

A: lose@水木清华 Unix

/usr/dt/bin/sdtimage *.ps

18. 补丁相关问题

18.1 如何根据补丁号从Sun主站下载补丁

18.2 删除旧式补丁备份,释放被占用的磁盘空间

A: Sun Microsystems 1997-11-18

使用installpatch安装补丁时,缺省操作是保存原有文件,以便将来可以卸载补丁恢
复原貌。系统管理员可以删除这些备份文件,释放被占用的磁盘空间。旧式补丁数据
位于/var/sadm/pkg目录中,比如

# find /var/sadm/pkg -type f -name "*.Z" -print
/var/sadm/pkg/SUNWcsr/save/104560-01/undo.Z
/var/sadm/pkg/SUNWmfrun/save/103461-10/undo.Z
/var/sadm/pkg/SUNWsunpc/save/102924-06/obsolete.Z
/var/sadm/pkg/SUNWsunpc/save/102924-25/undo.Z
#

undo.Z 是对最后一次补丁安装的备份。obsolete.Z 是对倒数第二次补丁安装的备份。
在这个例子中,补丁102924安装过两次,一次是版本6, 一次是版本25。可以删除
obsolete.Z备份

# find /var/sadm/pkg -type f -name "obsolete.Z" -exec rm {} /;

也可以删除undo.Z备份

# find /var/sadm/pkg -type f -name "undo.Z" -exec rm {} /;

注意,一旦删除undo.Z备份,执行backoutpatch命令时报错,无法卸载现有补丁。

18.3 patchdiag如何使用

18.4 给Solaris 2.6安装推荐补丁集(未完成)

18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁

A: scz <scz@nsfocus.com>

# showrev -p | grep "^Patch: 105181"
Patch: 105181-05 Obsoletes: 105636-01, 105776-01 Requires: ...
#

Q: 怎样看Solaris已经装了哪些patch

A: tobago@水木清华 Unix 2002-07-03 21:48

1) showrev -p

显示系统中已经安装的所有补丁,普通用户即可执行。比如得到这个输出

Patch: 108869-15 Obsoletes: Requires: Incompatibles: Packages:
SUNWmibii, SUNWsasnm, SUNWsadmi, SUNWsacom, SUNWsadmx, ...

2) /usr/sbin/patchadd -p

同上,不过这是一个shell script,要求以root身份执行

3) $ pkgparam SUNWmibii PATCHLIST
108869-15
$

通用格式是"pkgparam <package> PATCHLIST",显示应用到指定包的所有补丁。

4) $ pkgparam SUNWsadmi PATCH_INFO_108869-15
Installed: Wed Mar 20 10:04:48 CST 2002 From: Sun8 Obsoletes:
Requires: Incompatibles:
$

通用格式是"pkgparam <package> PATCH_INFO_<patch-number>",显示何时从何
处安装了相应补丁到本机。

18.6 如何安装补丁

19. 终端相关问题

19.0 如何将stdin、stdout、stderr重定向到/dev/null

Q: 我使用了如下代码将stdin、stdout、stderr重定向到/dev/null

freopen( "/dev/null", "w", stdout );
freopen( "/dev/null", "w", stderr );
freopen( "/dev/null", "r", stdin );

这样做正确吗,是否使用"w+"或者"a"更正确一些。在很多代码中是这样完成重定
向的:

close( 0 );
close( 1 );
close( 2 );
open( "/dev/null", O_RDWR );
dup( 0 );
dup( 0 );

这两种方式中哪一种更好、更具可移植性。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

第一种方式不是总能达到目的。freopen()并不确保新的文件流描述符一定重用底层
原有文件句柄号。假如未能重用,向stderr流输出的标准I/O函数最终输出到
/dev/null,但那些向STDERR_FILENO句柄输出的标准I/O函数就没这么幸运了,可能
输出到一些不可预期的文件中去。换句话说,2号句柄此时不再是标准错误输出了。
比如:

write( 2, ... )

这样的调用存在安全问题。第二种方式可以避免上述问题,然而存在竞争环境问题。

现在看下述代码:

--------------------------------------------------------------------------
int fd = open( "/dev/null", O_RDWR );
/*
* handle failure of open() somehow
*/
dup2( fd, 0 );
dup2( fd, 1 );
dup2( fd, 2 );
if ( fd > 2 )
{
close( fd );
}
--------------------------------------------------------------------------

与第二种方式相比,这种代码是线程安全的。

有人认为对于后台守护进程做此类重定向操作浪费资源,建议直接关闭0、1、2号句
柄拉倒,这是非常不正确的。假设它们确实被关闭了,则一些普通数据文件句柄将等
于0、1、2。以2号句柄为例,某些库函数失败后会向2号句柄输出错误信息,这将破
坏原有数据。

D: 小四 <scz@nsfocus.com> 2002-04-25 16:47

2号句柄的此类安全问题在2002年4月23日得到了实际印证,可参看<<x86/FreeBSD
4.5-RELEASE IO Smash及S/Key机制分析>>。

1987年,Henry Spencer在setuid(7)手册页中做了如下建议,一切标准I/O句柄都可
能因关闭过而不再是真实的标准I/O句柄,在使用printf()一类的函数前,务必确认
这些句柄是期待中的标准I/O句柄。1991年,在comp news上有人重贴了这份文档。

内核补丁应该确保对于SUID、SGID进程而言,0、1、2号句柄不会被打开后指向一个
普通文件。这有很多实现方式,比如使它们全部指向/dev/null。这种限制不应该在
库函数一级实现,可能有些SUID、SGID程序直接使用系统调用。

stdin、stdout、stderr中某一个被关闭,都可能潜在存在问题。

1992年W. Richard Stevens在<<Advanced Programming in the UNIX Environment>>
中建议Daemon进程应该关闭所有不必要的文件句柄,并将stdin、stdout、stderr指
向/dev/null。

自1998年以来,OpenBSD内核中execve()里有一个检查,如果句柄0、1、2是关闭的,
就打开/dev/null,使之对应0、1、2号句柄。这样就可以安全地执行setuid程序了。
FreeBSD/NetBSD直至最近才再次暴露出类似问题,而Linux在glibc中做了一些检查。

但是,OpenBSD这个检查存在一个问题,当falloc()失败时,应该转向错误处理,而
不是简单地跳出循环。art在注释中指出了这点,却无人去修正它。

--------------------------------------------------------------------------
sys/kern/kern_exec.c

在一个循环中,内核试图打开/dev/null,使之对应0-2号句柄

(...)
if ( ( error = falloc( p, &fp, &indx ) ) != 0 )
{
break;
}
(...)
--------------------------------------------------------------------------

于是本地用户获得一个内核文件表项相关的竞争环境,可以获取root权限。

19.1 如何使Backspace键做删除操作,而不是显示^H

Q: Backspace键并未删除光标左面那个字符,仅仅显示^H,而DEL键完成了删除操作

A: Sun Microsystems 2001-03-08

执行"stty -a"将看到"erase = ^?",表示此时DEL键对应删除操作。

如果正在使用xterm,可以用"tset"命令设置控制字符对应的操作。其他窗口中,假
设目前使用/sbin/sh,尝试

$ stty erase ^H

这里^H的输入是Ctrl-H,某些时候可能需要Ctrl-V、Ctrl-H输入,还可以尝试

$ stty erase "^h"
$ stty erase "^H" (大小写不敏感)

这里输入"^H",就是两个字符,一个^,一个H。

同样,如果想恢复到DEL删除

$ stty erase ^?

这里^?的输入是DEL,某些时候可能需要Ctrl-V、DEL输入,还可以尝试

$ stty erase "^?"

这里输入"^?",就是两个字符,一个^,一个?。

为了永久保留这个设置,在所使用的shell初始化文件中增加设置命令,比如c shell
的".cshrc",其他shell的".login"。

作者:飞猫头衔:版主发布时间:  2004-12-23 18:02:08

第4楼: 
19.2 telnet时如何关闭本地回显

19.3 如何清空stdin的缓冲

A: law@bbs.apue.net

stdin->_IO_read_ptr = stdin->_IO_read_end;

不过这个办法实在不怎么样。一是只对glibc有效,不可移植。二是违背流的思想,
老老实实用fgets()好了。

A: scz <scz@nsfocus.com> 2003-01-12 19:16

tcflush( STDIN_FILENO, TCIFLUSH );

19.4 Linux Console下一按错键就叫,怎么关

A: windtear@bbs.tsinghua.edu.cn Linux版

有个1050110 背一下就可以了

echo -e "//33[10;50]//33[11;0]"

10 50 11 0

放到那些登录自启动脚本里

A: Sidos@smth.org

如果想在X下也没有这个声音,在/etc/inputrc里加上

set bell-style none

Q: 输完命令后是没声了,可从KDE回来之后又有了,请问能彻底关掉吗

A: TheCool@bbs.tsinghua.edu.cn Linux版

setterm -blength 0 -bfreq 0

D: 对于FreeBSD,如果你不想在XWindow下听到beep,可以"xset b off"

19.5 从stdin立即获取按键

Q: Linux/C编程环境,从标准输入stdin读取内容时,有无办法立即获取按键,而不
必等待换行。事实上我需要MS-DOS下的kbhit()、getch()函数。

有些人总是建议进入(n)curses环境,可我不想使用这种多此一举的技术。

A: Floyd Davidson <floyd@ptialaska.net>

我们就作者所提供的原始代码进行了一些移植、修正,手头系统有限,未做更广泛的
可移植性测试。

--------------------------------------------------------------------------
/*
* For x86/Linux Kernel 2.4.7-10
* gcc -DLinux -Wall -pipe -O3 -o input_demo input_demo.c
*
* For x86/FreeBSD 4.5-RELEASE
* gcc -DFreeBSD -Wall -pipe -O3 -o input_demo input_demo.c
*
* For SPARC/Solaris 8
* gcc -DSolaris -Wall -pipe -O3 -o input_demo input_demo.c
*
* kbhit() -- a keyboard lookahead monitor
* getch() -- a blocking single character input from stdin
*
* Plus a demo main() to illustrate usage.
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>

#ifdef Solaris
#include <sys/filio.h>
#endif

#undef TERMIOSECHO
#define TERMIOSFLUSH

/*
* getch() -- a blocking single character input from stdin
*
* Returns a character, or -1 if an input error occurs
*
* Conditionals allow compiling with or without echoing of the input
* characters, and with or without flushing pre-existing buffered input
* before blocking.
*/
static int getch ( void )
{
struct termios old_termios, new_termios;
int error;
char c;

fflush( stdout );
tcgetattr( 0, &old_termios );
new_termios = old_termios;
/*
* raw mode, line settings
*/
new_termios.c_lflag &= ~ICANON;
#ifdef TERMIOSECHO
/*
* enable echoing the char as it is typed
*/
new_termios.c_lflag |= ECHO;
#else
/*
* disable echoing the char as it is typed
*/
new_termios.c_lflag &= ~ECHO;
#endif
#ifdef TERMIOSFLUSH
/*
* use this to flush the input buffer before blocking for new input
*/
#define OPTIONAL_ACTIONS TCSAFLUSH
#else
/*
* use this to return a char from the current input buffer, or block
* if no input is waiting
*/
#define OPTIONAL_ACTIONS TCSANOW
#endif
/*
* minimum chars to wait for
*/
new_termios.c_cc[VMIN] = 1;
/*
* minimum wait time, 1 * 0.10s
*/
new_termios.c_cc[VTIME] = 1;
error = tcsetattr( 0, OPTIONAL_ACTIONS, &new_termios );
if ( 0 == error )
{
/*
* get char from stdin
*/
error = read( 0, &c, 1 );
}
/*
* restore old settings
*/
error += tcsetattr( 0, OPTIONAL_ACTIONS, &old_termios );
return( error == 1 ? ( int )c : -1 );
} /* end of getch */

/*
* kbhit() -- a keyboard lookahead monitor
*
* returns the number of characters available to read
*/
static int kbhit ( void )
{
struct timeval tv;
struct termios old_termios, new_termios;
int error;
int count = 0;

tcgetattr( 0, &old_termios );
new_termios = old_termios;
/*
* raw mode
*/
new_termios.c_lflag &= ~ICANON;
/*
* disable echoing the char as it is typed
*/
new_termios.c_lflag &= ~ECHO;
/*
* minimum chars to wait for
*/
new_termios.c_cc[VMIN] = 1;
/*
* minimum wait time, 1 * 0.10s
*/
new_termios.c_cc[VTIME] = 1;
error = tcsetattr( 0, TCSANOW, &new_termios );
tv.tv_sec = 0;
tv.tv_usec = 100;
/*
* insert a minimal delay
*/
select( 1, NULL, NULL, NULL, &tv );
error += ioctl( 0, FIONREAD, &count );
error += tcsetattr( 0, TCSANOW, &old_termios );
return( error == 0 ? count : -1 );
} /* end of kbhit */

int main ( int argc, char * argv[] )
{
struct termios old_termios, new_termios;
int count;
int c;

tcgetattr( 0, &old_termios );
printf( "You must enter 10 characters to get this program to continue:" );
fflush( stdout );
/*
* collect 10 characters
*/
for ( count = kbhit(); count < 10; count = kbhit() )
{
if ( -1 == count )
{
return( EXIT_FAILURE );
}
}
new_termios = old_termios;
/*
* disable echoing of further input
*/
new_termios.c_lflag &= ~ECHO;
tcsetattr( 0, TCSANOW, &new_termios );
printf( "/nStop, now type <Enter> to continue" );
fflush( stdout );
c = getchar();
/*
* enable echoing of further input
*/
tcsetattr( 0, TCSANOW, &old_termios );
printf( "/nThe first five characters are: [" );
/*
* print a few chars
*/
for ( count = 0; count < 4; count++ )
{
printf( "%c", ( char )c );
c = getchar();
}
printf( "%c]/n/n", ( char )c );
printf( "****** Demo Menu ******/n/n" );
printf( "Option Action/n" );
printf( " A Action_A/n" );
printf( " B Action_B/n" );
printf( " C Action_C/n" );
printf( " Q Exit/n/n" );
printf( "Enter your choice: [ ]/b/b" );
fflush( stdout );
/*
* note that calling getch() will flush remaining buffered input
*/
switch ( c = getch() )
{
case 'a':
case 'A':
printf( "%c/nAction_A/n", ( char )toupper( ( int )c ) );
break;
case 'b':
case 'B':
printf( "%c/nAction_B/n", ( char )toupper( ( int )c ) );
break;
case 'c':
case 'C':
printf( "%c/nAction_C/n", ( char )toupper( ( int )c ) );
break;
case 'q':
case 'Q':
printf( "%c/nExit/n", ( char )toupper( ( int )c ) );
break;
default:
printf( "%c/n", ( char )toupper( ( int )c ) );
break;
}
tcsetattr( 0, TCSANOW, &old_termios );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

D: 小四 <scz@nsfocus.com>

c_cc[VTIME]以0.10秒为单位指定一个字节间的读取超时,所对应的计时器在接收到
第一个字节之后才启动,因此c_cc[VMIN]为1的时候,c_cc[VTIME]是多少都无所谓。
关于这方面的详细讨论参看APUE 11.11小节。

input_demo.c:main()中第一个getchar()调用,其本意是在"规范模式"下阻塞,等待
一个换行。SPARC/Solaris 8下语义与x86/Linux Kernel 2.4.7-10、x86/FreeBSD
4.5-RELEASE不同,在此处未能阻塞住,我怀疑是该版本实现上的一个BUG。

19.6 如何屏蔽Ctrl-D

Q: Ctrl-C产生SIGINT信号,Ctrl-D产生什么信号。我想屏蔽Ctrl-D。

A: W. Richard Stevens

参看APUE 11.3小节的讨论。

Ctrl-C并不总是固定产生SIGINT信号,只能说通常如此。stty -a命令往往可以看到

intr = ^C; eof = ^D;

由当前终端属性决定,而这可以编程改变,或者就用stty命令设置。Ctrl-D通常导致
阻塞读操作返回EOF,并不产生任何信号,除非刻意设置过。

顺便说一句,为了指示文件结束,需要在"新行行首"输入EOF(通常是Ctrl-D)。

--------------------------------------------------------------------------
/*
* For x86/Linux Kernel 2.4.7-10
* x86/FreeBSD 4.5-RELEASE
* SPARC/Solaris 8
* gcc -Wall -pipe -O3 -o ctrl_cd ctrl_cd.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>

int main ( int argc, char * argv[] )
{
struct termios term;
long vdisable = 0;
char buf[80];

if ( isatty( STDIN_FILENO ) == 0 )
{
fprintf( stderr, "standard input is not a terminal device/n" );
return( EXIT_FAILURE );
}
vdisable = fpathconf( STDIN_FILENO, _PC_VDISABLE );
if ( vdisable < 0 )
{
perror( "fpathconf( STDIN_FILENO, _PC_VDISABLE ) error" );
return( EXIT_FAILURE );
}
if ( tcgetattr( STDIN_FILENO, &term ) < 0 )
{
perror( "tcgetattr( STDIN_FILENO, &term ) error" );
return( EXIT_FAILURE );
}
/*
* disable INTR character
*/
term.c_cc[VINTR] = vdisable;
/*
* EOF is Ctrl-B
*/
term.c_cc[VEOF] = 2;
if ( tcsetattr( STDIN_FILENO, TCSAFLUSH, &term ) < 0 )
{
perror( "tcsetattr( STDIN_FILENO, TCSAFLUSH, &term ) error" );
return( EXIT_FAILURE );
}
while ( fgets( buf, sizeof( buf ), stdin ) != NULL );
if ( feof( stdin ) != 0 )
{
printf( "fgets() found EOF/n" );
}
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

由于我们修改了c_cc[VINTR]、c_cc[VEOF]的标准设置,在测试ctrl_cd.c时,Ctrl-C
与Ctrl-D不再有效,需要用Ctrl-B输入EOF来结束运行。

ctrl_cd.c中没有恢复c_cc[VINTR]、c_cc[VEOF]的标准设置,可以简单利用stty命令
恢复:

stty intr ^C
stty eof ^D

这里^C用Ctrl-v、Ctrl-c输入,^D用Ctrl-v、Ctrl-d输入。

20. shell script问题

20.0 不用临时文件完成字符串替换

20.1 如何获取一个字符串的长度

A: Andrei Ivanov <iva@racoon.riga.lv>

expr `echo $string | wc -c` - 1

echo $string | awk '{ print length( $0 ); }'

/usr/ucb/expr length "$string"

expr "$string" : ".*"

echo "$string" | sed 's/./1+/g;s/+/ /;s/$/p/' | dc

A: http://www.linuxforum.net

假设是bash

$ string='1234567890'
$ echo ${#string}
10
$

20.2 读超时自动使用缺省值

Q: shell script编程,不介入expect、perl、tcl等类似工具。读等待60秒,超时则
自动使用缺省值。可以使用系统缺省外部命令,要求能广泛移植在常用Unix平台

A: CERNET 华中地区网络中心 PUE(UNIX环境程序设计)版 lookout

参看comp.unix.shell新闻组,下面以SPARC/Solaris 2.6为例

--------------------------------------------------------------------------
#! /sbin/sh
stty -icanon min 0 time 255
while true
do
/usr/bin/echo "Press a key or press ENTER to exit:/c"
read key
if [ "$key" = "" ] ; then
echo "/nYou press Enter or timeout"
break
else
echo "You press the key $key"
fi
done
stty sane
--------------------------------------------------------------------------

D: scz <scz@nsfocus.com>

参看termio(7I)、stty(1)手册页了解更多关于"非规范模式"的信息,下面这个C程序
揭示了上述shell script的本质

--------------------------------------------------------------------------
/*
* For x86/Linux、x86/FreeBSD、SPARC/Solaris
* gcc -Wall -pipe -O3 -o keypress keypress.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>

static struct termios originalTermParam;

static void set_keypress ( void )
{
struct termios currentTermParam;

tcgetattr( 0, &originalTermParam );
memcpy( ¤tTermParam, &originalTermParam, sizeof( struct termios ) );

/*
* Disable canonical mode, and set buffer size to 1 byte
*/
currentTermParam.c_lflag &= ~ICANON; /* 不设置则允许最快速的读取字符 */
currentTermParam.c_lflag &= ~ECHO; /* 不回显 */
/*
* 超时设置,255 * 0.10s,单位是0.10s
* 这里是一个字节,所以最大值为255
*/
currentTermParam.c_cc[ VTIME ] = 255;
/*
* 无阻塞输入,CPU占用率很高
* 如果该值为1,表示至少等待输入一个字符,阻塞
*/
currentTermParam.c_cc[VMIN] = 0;

tcsetattr( 0, TCSANOW, ¤tTermParam );
return;
} /* end of set_keypress */

void reset_keypress ( void )
{
tcsetattr( 0, TCSANOW, &originalTermParam );
return;
} /* end of reset_keypress */

int main ( void )
{
puts( "main start" );
set_keypress();
while ( 1 )
{
if ( getchar() == ( int )'q' )
{
break;
}
}
reset_keypress();
puts( "main stop" );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

D: 小四 <scz@nsfocus.com> 2001-07-04 13:12

下面以SPARC/Solaris 2.6为例,提供一个GetYesOrNo()函数,第一形参指定超时缺
省返回值,第二形参指定超时时限(以0.10秒为单位),后续形参指定提示信息。

--------------------------------------------------------------------------
#! /bin/sh

GetYesOrNo ()
{
# 第一形参指定超时缺省返回值
case ${1} in
0 )
EXITCODE=${1};;
* )
# 非零皆为1
EXITCODE=1;;
esac
# 第二形参指定超时时限(以0.10秒为单位)
if [ ${2} -le 255 -a ${2} -ge 0 ] ; then
TIMEOUT=${2}
else
# 缺省超时时限5秒
TIMEOUT=50
fi
shift 2
SAVETERM=`stty -g`
# 设置xxx秒后读超时,非规范模式
stty -icanon min 0 time ${TIMEOUT}
# Solaris下,sh的内部命令echo支持/c这种用法
while echo "$* (Y/N) ?/c" >&2
do
read YesOrNo
case "$YesOrNo" in
[yY] )
EXITCODE=0
break;;
[nN] )
EXITCODE=1
break;;
"" )
# 超时或者选择使用缺省值
break;;
* )
echo "/nPlease enter Y(y) or N(n) ... ..." >&2;;
esac
done
# stty sane
stty ${SAVETERM}
echo ""
return ${EXITCODE}
}

GetYesOrNo 0 50 "Please select"
exit ${?}
--------------------------------------------------------------------------

20.3 如何删除空行、空白符组成的行

20.4 BASH中如何得到一个字符串的子串

20.5 shell script中如何关闭stdout

20.6 如何将一个文本文件开始的N行删除

20.7 以字符串(非单个字符)为分隔的析取

20.8 使用tr命令加密文件

A: 水木清华 TheCool

著名的 rot13 密码, 通过把字母移动13个位置实现对文本的加密

tr "[a-m][n-z][A-M][N-Z]" "[n-z][a-m][N-Z][A-M]" < message > newmessage

然后可以用同样的命令进行解密

tr "[a-m][n-z][A-M][N-Z]" "[n-z][a-m][N-Z][A-M]" < newmessage > message

20.9 有哪些命令用于查找定位

A: 小四

type -a telnet
whereis telnet
which telnet
whatis telnet <=> man -k telnet

20.10 非递归删除目录树

A: 小四 <scz@nsfocus.com>

先来看一个显示目录树的经典shell script

--------------------------------------------------------------------------
#! /bin/sh
# Author: Kenneth H. Rosen
# Richard R. Rosinski
# Douglas A. Host
# Test for x86/FreeBSD

#
# ${parameter:-default_value} 如果参数未赋值,将缺省值赋给它
#
dir=${1:-.}
(cd ${dir};pwd)

#
# "s,^${dir},," 删除最前面的一层目录
# "/^$/d" 删除空行
# "s,[^/]*//([^/]*/)$,/`----/1," 将最后一个/替换成`----
# "s,[^/]*/, ,g" 从左向右,将此时所有的*/替换成五个空格
#
find ${dir} -type d -print | sort -f | sed -e "s,^${dir},," /
-e "/^$/d" -e "s,[^/]*//([^/]*/)$,/`----/1," -e "s,[^/]*/, ,g"
--------------------------------------------------------------------------

下面是伪shell script,非递归删除目录树,foreach、goto都是csh才有的内部命令

--------------------------------------------------------------------------
cd target_directory
target_directory = `pwd`
while ( true )
{

loop:

rm -f *
foreach ( 本层目录所有项 )
{
if ( 子目录 )
{
rmdir 子目录
if ( 删除失败 )
{
cd 子目录
goto loop;
}
}
else
{
rm -f 普通文件
}
}
if ( `pwd` == target_directory )
{
break;
}
else
{
cd ..
}
}
cd ..
rmdir target_directory
--------------------------------------------------------------------------

下面是一个csh script,非递归删除目录树

--------------------------------------------------------------------------
#! /bin/csh -f
# Test for x86/FreeBSD
# Author : NSFocus Security Team
# : http://www.nsfocus.com
# Maintain : scz <scz@nsfocus.com>
# Usage : ./csh_rm <target_directory>
# Date : 2002-01-10 15:57

#
# 必须指定第一形参
#
if ( $#argv != 1 ) then
echo "Usage: ${0} <target_directory>"
exit 1
endif

#
# 第一形参必须是目录
#
if ( ! -d ${1} ) then
echo "Usage: ${0} <target_directory>"
exit 1
endif

#
# 这里可以先rm -rf ${1},如果失败,再用下面的方式删除
#
cd ${1}
#
# 注意csh的赋值与sh不同,需要set命令的介入
#
set target_directory = "`pwd`"
# echo ${target_directory}

alias ls '/bin/ls -1a'

#
# csh支持0、1,不支持false、true
#
while ( 1 )

loop:

#
# 对于csh,>& 表示将stdout、stderr同时转向/dev/null
#
# 其实这里可以用rm -rf * >& /dev/null,下面这句可以注释掉
#
# rm -f * >& /dev/null
#
# 不知道为什么,foreach entry ( * )存在缓冲现象,导致失败,被迫使用现
# 在演示的技术
#
foreach entry ( `ls` )
if ( "${entry}" == "." || "${entry}" == ".." ) then
continue
endif
if ( -d ${entry} ) then
#
# 这里可以用rm -rf ${entry} >& /dev/null
#
rmdir ${entry} >& /dev/null
if ( $? != 0 ) then
cd ${entry}
goto loop;
endif
else
rm -f ${entry} >& /dev/null
endif
end
set current_directory = "."
#
# 当路径超长时,pwd会报错,不必理会这个错误信息,csh无法单独处理stderr
#
set current_directory = "`pwd`"
#
# if ( "`pwd`" == "${target_directory}" ) then
#
# 如果这样写,我不确认当pwd失败时,是否会继续判断,至少目前的写法得到
# 验证,可行
#
if ( "${current_directory}" == "${target_directory}" ) then
break;
else
cd .. >& /dev/null
endif
end
cd .. >& /dev/null
rmdir ${target_directory} >& /dev/null
--------------------------------------------------------------------------

$ cp -R /tmp . <-- 为了安全起见,不要以root身份测试这个脚本
$ cp -R tmp tmp <-- 最终会出错
$ ./csh_rm tmp <-- 验证这个csh script

20.11 如何将大写文件名转换为小写文件名

A: Potash@www.linuxforum.net 2002-02-05 18:58

--------------------------------------------------------------------------
#! /bin/sh
# Usage: ./loworup.sh <-l | -u> <target_directory>

#
# 第二形参必须是目录,第一形参指定-l或-u
#
if [ $# -ne 2 ] ; then
echo "Usage: ${0} <-l | -u> <target_directory>"
exit 1
fi

if [ ! -d ${2} -o "${1}" != "-l" -a "${1}" != "-u" ] ; then
echo "Usage: ${0} <-l | -u> <target_directory>"
exit 1
fi

exec 1>/dev/null 2>&1

dir=`dirname "${2}"`
cd ${dir}

if [ "${1}" = "-l" ] ; then
base=`basename "${2}" | tr "[A-Z]" "[a-z]"`
else
base=`basename "${2}" | tr "[a-z]" "[A-Z]"`
fi

mv -f "`basename ${2}`" "${base}"

for entry in `find ${base}`
do
before="."
#
# 这个办法依赖for in语法,用空格做分隔符,所以不能处理那些本身名字带空
# 格的目录项,属于小BUG
#
for after in `echo "${entry}" | sed -e 's,/, ,g'`
do
tmp_entry="${before}/${after}"
if [ "${1}" = "-l" ] ; then
before=`echo "${tmp_entry}" | tr "[A-Z]" "[a-z]"`
else
before=`echo "${tmp_entry}" | tr "[a-z]" "[A-Z]"`
fi
mv -f "${tmp_entry}" "${before}"
done
done
--------------------------------------------------------------------------

20.12 shell script中有办法实现段落注释吗

Q: C编程中"#if 0"可以快速实现段落注释,shell script中如何达到同样效果。

A: windtear@bbs.tsinghua.edu.cn

cat << EOF > /dev/null
... ...
EOF

或者

echo << EOF > /dev/null
...
EOF

推荐使用echo。

20.13 批量文件字符串替换

A: scz <scz@nsfocus.com>

--------------------------------------------------------------------------
#! /bin/sh
# Usage: ./replace.sh <filelist> <srcstr> <dststr>

if [ $# -ne 3 ] ; then
echo "Usage: ${0} <filelist> <srcstr> <dststr>"
exit 1
fi
#
# 第一形参必须是普通文件
#
if [ ! -f ${1} ] ; then
echo "Checking your <filelist>"
exit 1
fi

while read LINE
do
if [ "${LINE}" = "" ]
then
continue
fi
# awk是为了配合grep操作,减少工作量
LINE_FILE=`echo "${LINE}" | awk -F: '{print $1;}'`
# 第一个,相当于1,$
ed - "${LINE_FILE}" << EOF
,s,${2},${3},g
w
q
EOF
done < "${1}"
--------------------------------------------------------------------------

21. BSD相关问题

21.0 在x86/FreeBSD 4.5-RELEASE上安装nessus

21.1 如何将/var文件系统mount成mfs并支持cron daemon

21.2 如何将一个512字节的文件写入主引导扇区

A: All of DOS Programmers 2001-10-16 18:05

这个问题如果在90年代初MS-DOS盛行的时候出现,是要被人砍死的,如今时过境迁,
居然能进入这份Unix文档,权当是一种追忆吧。所谓主引导扇区就是硬盘0柱面、0磁
头、1扇区。启动DEBUG,

-f 0200 l 0200 0 <-- 从0200h处开始清零,长512字节
-n mbr <-- 假设我们的要处理的文件名为mbr
-l 0200 <-- 读到0200h处
-d 03be 03ff <-- 检查分区表
XXXX:03B0 00 00 ..
XXXX:03C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA ..............U.
-

-a 100 <-- 读主引导扇区的汇编代码
XXXX:0100 mov ax, 0201 <-- 读取一个扇区,ah=02(功能码),al=01(扇区总数)
XXXX:0103 mov bx, 0400 <-- 读取后存放在0400h处,长512字节
XXXX:0106 mov cx, 0001 <-- ch=00(柱面号,10bit),cl=01(扇区号,6bit)
XXXX:0109 mov dx, 0080 <-- dh=00(磁头号),dl=80h(驱动器号)
XXXX:010C int 13 <-- int 13h 磁盘I/O BIOS
XXXX:010E int 3 <-- 单步中断,可以换成int 20h
XXXX:010F
-g=100 <-- 从0100h处开始执行

AX=0050 BX=0400 CX=0001 DX=0080 SP=FFEE BP=0000 SI=0000 DI=0000
DS=XXXX ES=XXXX SS=XXXX CS=XXXX IP=010E NV UP EI PL NZ NA PO NC
XXXX:010E CC INT 3
-d 05be 05ff <-- 检查分区表
XXXX:05B0 80 01 ..
XXXX:05C0 01 00 06 FE 3F 7F 3F 00-00 00 41 60 1F 00 00 00 ....?.?...A`....
XXXX:05D0 01 80 0F FE FF FF 80 60-1F 00 22 3C A0 01 00 00 .......`.."<....
XXXX:05E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:05F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA ..............U.
-

-m 05be l 40 03be <-- 复制分区表
-d 03be l 40 <-- 确认分区表复制成功
XXXX:03B0 80 01 ..
XXXX:03C0 01 00 06 FE 3F 7F 3F 00-00 00 41 60 1F 00 00 00 ....?.?...A`....
XXXX:03D0 01 80 0F FE FF FF 80 60-1F 00 22 3C A0 01 00 00 .......`.."<....
XXXX:03E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 ..............
-a 100 <-- 写主引导扇区的汇编代码
XXXX:0100 mov ax, 0301 <-- 写一个扇区,ah=03(功能码),al=01(扇区总数)
XXXX:0103 mov bx, 0200 <-- 待写入数据存放在0200h处,长512字节
XXXX:0106 mov cx, 0001 <-- ch=00(柱面号,10bit),cl=01(扇区号,6bit)
XXXX:0109 mov dx, 0080 <-- dh=00(磁头号),dl=80h(驱动器号)
XXXX:010C int 13 <-- int 13h 磁盘I/O BIOS
XXXX:010E int 3 <-- 单步中断,可以换成int 20h
XXXX:010F
-g=100 <-- 从0100h处开始执行
-q <-- 退出DEBUG

第2个硬盘驱动器号是81h,修改DX寄存器赋值语句即可。

A: All of Solaris/FreeBSD/Linux Users

dd if=<path to file> of=/dev/... bs=512 count=1
^^^^^^^^ 对应要处理的物理硬盘设备

参看如下例子

# ls -li /dev/rad0 <-- 注意inode号,这里r应该是raw的含义
8051 crw-r----- 2 root operator 116, 0x00010002 /dev/rad0
# ls -li /dev/ad0
8051 crw-r----- 2 root operator 116, 0x00010002 /dev/ad0
# dd if=/dev/ad0 of=/tmp/mbr bs=512 count=1
1+0 records in
1+0 records out
512 bytes transferred in 0.000363 secs (1410498 bytes/sec)
# hexdump -C /tmp/mbr
00000000 fc 31 c0 8e c0 8e d8 8e d0 bc 00 7c be 1a 7c bf |.1.........|..|.|
00000010 1a 06 b9 e6 01 f3 a4 e9 00 8a 31 f6 bb be 07 b1 |..........1.....|
00000020 04 38 2f 74 08 7f 78 85 f6 75 74 89 de 80 c3 10 |.8/t..x..ut.....|
00000030 e2 ef 85 f6 75 02 cd 18 80 fa 80 72 0b 8a 36 75 |....u......r..6u|
00000040 04 80 c6 80 38 f2 72 02 8a 14 89 e7 8a 74 01 8b |....8.r......t..|
00000050 4c 02 bb 00 7c 80 fe ff 75 32 83 f9 ff 75 2d 51 |L...|...u2...u-Q|
00000060 53 bb aa 55 b4 41 cd 13 72 20 81 fb 55 aa 75 1a |S..U.A..r ..U.u.|
00000070 f6 c1 01 74 15 5b 66 6a 00 66 ff 74 08 06 53 6a |...t.[fj.f.t..Sj|
00000080 01 6a 10 89 e6 b8 00 42 eb 05 5b 59 b8 01 02 cd |.j.....B..[Y....|
00000090 13 89 fc 72 0f 81 bf fe 01 55 aa 75 0c ff e3 be |...r.....U.u....|
000000a0 bc 06 eb 11 be d4 06 eb 0c be f3 06 eb 07 bb 07 |................|
000000b0 00 b4 0e cd 10 ac 84 c0 75 f4 eb fe 49 6e 76 61 |........u...Inva|
000000c0 6c 69 64 20 70 61 72 74 69 74 69 6f 6e 20 74 61 |lid partition ta|
000000d0 62 6c 65 00 45 72 72 6f 72 20 6c 6f 61 64 69 6e |ble.Error loadin|
000000e0 67 20 6f 70 65 72 61 74 69 6e 67 20 73 79 73 74 |g operating syst|
000000f0 65 6d 00 4d 69 73 73 69 6e 67 20 6f 70 65 72 61 |em.Missing opera|
00000100 74 69 6e 67 20 73 79 73 74 65 6d 00 00 00 00 00 |ting system.....|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 01 |................|
000001c0 01 00 a5 0f ff ff 3f 00 00 00 b1 ff 3f 00 00 00 |......?.....?...|
000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200

D: 硬盘主引导扇区 2002-03-04 14:12

软盘没有主引导代码、分区表这些概念,我们不做讨论。下面是主引导扇区的图解

0000h +---------------------------------------------------------+
| |
| Master Boot Record |
| (主引导代码 446字节) |
01BDh | |
01BEh +---------------------------------------------------------+
| |
| 分区表项1(16字节) |
01CDh | |
01CEh +---------------------------------------------------------+
| |
| 分区表项2(16字节) |
01DDh | |
01DEh +---------------------------------------------------------+
| |
| 分区表项3(16字节) |
01EDh | |
01EEh +---------------------------------------------------------+
| |
| 分区表项4(16字节) |
01FDh | |
01FEh +---------------------------------------------------------+
| 0x55 (01FEh) | 0xAA (01FFh) |
+---------------------------------------------------------+

这就是硬盘硬盘0柱面、0磁头、1扇区(主引导扇区)的基本结构。下面介绍单个分区
表项(16字节)

每个硬盘分区表项长16字节,内容如下

第1字节 是一个分区的激活标志,0表示非活动分区,80h表示活动分区。

第2字节 该分区起始磁头号(从0开始)

8bit的磁头号可以表示256个磁头

第3字节 bit7-bit6 与第4字节一起使用

bit5-bit0 该分区起始扇区号(从1开始)

6bit的扇区号只能表示64个扇区,因为这里从1开始,所以最后只能表示63
个扇区

第4字节 bit7-bit0

第3字节的bit7-bit6作为高两位与第4字节bit7-bit0一起构成10bit的起始
柱面号,表示该分区起始柱面号(从0开始)

10bit的柱面号只能表示1024个柱面

第5字节 该分区系统类型标志,对于FreeBSD是A5h(165)

第6字节 该分区结束磁头号,与第2字节相对应

第7字节 bit7-bit6 与第8字节一起使用

bit5-bit0 该分区结束扇区号

与第3字节对应

第8字节 bit7-bit0

第7字节的bit7-bit6作为高两位与第8字节bit7-bit0一起构成10bit的结束
柱面号,表示该分区结束柱面号

与第4字节对应

第9-12字节(4)

该分区首扇区的逻辑扇区号(从0开始)

第13-16字节(4)

该分区占用的扇区总数

关察下列分区表中第二项

--------------------------------------------------------------------------
00 00 00 00 00 00 00 00 00 00-00 00 00 00 00 00
80 00 01 02 A5 FE 3F 33 82 7D-00 00 B2 41 0C 00
00 00 00 00 00 00 00 00 00 00-00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00-00 00 00 00 00 00
55 AA
--------------------------------------------------------------------------

80h活动分区,00起始磁头,01起始扇区,02起始柱面,A5h分区类型。FEh结束磁头,
3Fh结束扇区,33h结束柱面,00007D82h分区首扇区的逻辑扇区号,000C41B2h分区占
用的扇区总数。

00007D82h = 32130

000C41B2h = 803250

Q: 在MS-DOS下执行fdisk /mbr,究竟做了什么

A: 只是修改了446字节(01BEh)的主引导代码,并未修改64字节(40h)的分区表。

D: 袁哥 <yuange@nsfocus.com>

每个硬盘分区表项长16字节,内容如下

第1字节 是一个分区的激活标志,0表示非活动分区,80h表示活动分区。
第2字节 该分区起始磁头号(从零开始)
第3字节 该分区起始扇区号(从一开始)
第4字节 该分区起始柱面号(从零开始)
第5字节 该分区系统类型标志

第6-8字节(3)

该分区结束磁头号、分区结束扇区号、分区结束柱面号

第9-12字节(4)

该分区首扇区的逻辑扇区号(从零开始)

第13-16字节(4)

该分区占用的扇区总数

关察下列分区表中第二项

00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 80 00
01 02 A5 FE 3F 33 82 7D-00 00 B2 41 0C 00 00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA

80h活动分区,00起始磁头,01起始扇区,02起始柱面,A5h分区类型。FEh结束磁头,
3Fh结束扇区,33h结束柱面,00007D82h分区首扇区的逻辑扇区号,000C41B2h分区占
用的扇区总数

00007D82h = 32130

000C41B2h = 803250

D: IamRichard@bbs.gznet.edu.cn

对第3、4字节的解释,bit0-bit5为起始扇区号,bit6-bit7为起始柱面号的高两位,
即第3字节的bit6-bit7作为高两位与第4字节一起构成10bit的起始柱面号。第7、8字
节亦有相似解释。10bit的柱面号只能表示1024个柱面。

D: WYHui@bbs.gznet.edu.cn

0磁道、0柱面、2扇区~0磁道、0柱面、63扇区在缺省情况下均是保留扇区,但可以
使用,只要保证没有别人使用就行。下面是MBR源代码

--------------------------------------------------------------------------
PartLoad equ 600h
BootLoc equ 7c00h
.MODEL tiny
.CODE
org 0
Head:
Start:
cli
xor ax,ax
mov ss,ax
mov sp,7c00h
mov si,sp
push ax
pop es
push ax
pop ds
sti
cld
mov di,PartLoad
mov cx,100h
repne movsw
db 0eah
dw offset Continue+600h,0000h
Continue:
mov si,PartLoad+1beh
mov bl,4
FindBoot:
cmp byte ptr[si],80h
je SaveRec
cmp byte ptr[si],0
jne Invalid
add si,10h
dec bl
jnz FindBoot
int 18h
SaveRec:
mov dx,[si]
mov cx,[si+2]
mov bp,si
FindNext:
add si,10h
dec bl
jz SetRead
cmp byte ptr[si],0
je FindNext
Invalid:
mov si,offset ErrMsg1+600h
PrintStr:
lodsb
cmp al,0
je DeadLock
push si
mov bx,7
mov ah,0eh
int 10h
pop si
jmp short PrintStr
DeadLock:
jmp short DeadLock
SetRead:
mov di,5
ReadBoot:
mov bx,BootLoc
mov ax,201h
push di
int 13h
pop di
jnc GoBoot
xor ax,ax
int 13h
dec di
jnz ReadBoot
mov si,offset ErrMsg2+600h
jmp short PrintStr
GoBoot:
mov si,offset ErrMsg3+600h
mov di,7c00h+1feh
cmp word ptr[di],0aa55h
jne PrintStr
mov si,bp
db 0eah,00h,7ch,00h,00h
ErrMsg1 db 'Invalid Partition table',0
ErrMsg2 db 'Error loading operating system',0
ErrMsg3 db 'Missing operating system',0
Tail:
FillNum equ 1beh-(Tail-Head)
db Fillnum dup(0)
PartTable:
table1 db ' '
table2 db ' '
table3 db ' '
table4 db ' '
MagicID dw 0aa55h
End Start
--------------------------------------------------------------------------

21.3 x86/FreeBSD 4.3-RELEASE下FDISK(8)手册页

21.4 x86/FreeBSD 4.3-RELEASE下HEXDUMP(1)手册页

21.5 x86/FreeBSD 4.3-RELEASE下DISKLABEL(8)手册页

21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel

Q: 重新编译内核后用cp命令无法覆盖/kernel

A: deepin <deepin@nsfocus.com>

# ls -lo /kernel
-rwxr-xr-x 1 root wheel schg /kernel*
^^^^注意这里,类似Linux的chattr那些东西
# chflags noschg /kernel

参看CHFLAGS(1)、INSTALL(1)手册页。这样修改后可以cp覆盖/kernel了。最后恢复
chflags设置

# chflags schg /kernel

21.7 x86/FreeBSD下如何设置路由

A: backend <backend@nsfocus.com> 2001-10-25 11:33

/etc/defaults/rc.conf或者/etc/rc.conf中会有这样的设置

--------------------------------------------------------------------------
defaultrouter="NO" # Set to default gateway (or NO).
static_routes="" # Set to static route list (or leave empty).
--------------------------------------------------------------------------

下面分析static_routes的用法,从/etc/rc.network脚本中可以看到这样的处理

--------------------------------------------------------------------------
# Configure routing
#
case ${defaultrouter} in
[Nn][Oo] | '')
;;
*)
static_routes="default ${static_routes}"
route_default="default ${defaultrouter}"
;;
esac

# Set up any static routes. This should be done before router discovery.
#
if [ -n "${static_routes}" ]; then
for i in ${static_routes}; do
eval route_args=/$route_${i}
route add ${route_args}
done
fi
--------------------------------------------------------------------------

注意eval命令导致二次变量替换,对上述脚本分析后可知static_routes用法如下

--------------------------------------------------------------------------
defaultrouter="<IP>"
static_routes="<name1> <name2> ..."
route_<name1>="符合route add命令的语法格式"
route_<name2>="符合route add命令的语法格式"
... ...
--------------------------------------------------------------------------

举例说明

--------------------------------------------------------------------------
defaultrouter="192.168.0.1"
static_routes="entry1 entry2"
route_entry1="-net 10.10.1.0 -netmask 255.255.255.0 -gateway 192.168.254.1"
route_entry2="-net 10.10.2.0 -netmask 255.255.255.0 -gateway 192.168.254.2"
--------------------------------------------------------------------------

当然,你可以不用两个rc.conf文件,而是在/etc/rc.local中直接用route命令增加
路由。

21.8 x86/FreeBSD 4.4-RELEASE下DIFF(1)手册页

21.9 什么是locale

A: Shen Chuan-Hsing <statue@freebsd.sinica.edu.tw>

本文来自<<FreeBSD Chinese HOWTO>>,参看如下链接
http://freebsd.sinica.edu.tw/~statue/zh-tut/

locale 指定一组C语言处理自然语言(文字)的方式,也可以简单地说,locale反映了
一组"地区性语言"的配置信息

LC_ALL 代表所有的locale(如下)

LC_CTYPE 字符定义(包含字符分类与转换规则)

LC_MESSAGES 信息显示

LC_TIME 时间格式

LC_NUMERIC 数字格式

LC_MONETARY 货币格式

LC_COLLATE 字母顺序与特殊字符比较顺序

其中与一般使用者息息相关的是是LC_CTYPE与LC_MESSAGES。LC_CTYPE直接关系到某
些字符或內码在目前locale下是否可显示?要如何转换编码?对应到哪一个字?等等。
LC_MESSAGES则关系到软件的信息输出是否符合地域性,例如:我们需要的是中文。
而一个真正完整支持locale系统,是当使用者在shell prompt下,直接设置好环境变
量后就马上切换到那种语言了,例如:

% export LC_CTYPE=zh_TW.Big5

设置locale的字符定义为台湾地区的Big5繁体中文码定义。有了正确的locale定义后,
使得任何地区的的文字,只要在加入适当的locale data之后,C Library就能正确地
处理软件显示信息,而我们使用的[中文]当然也不例外。

21.10 用cvsup安装vim

21.11 FreeBSD下显示、输入中文

21.12 如何在OpenSSH中限制只允许某些用户登录

A: tt <warning3@nsfocus.com> 2001-11-20 10:56

编辑/etc/ssh/sshd_config,在最后增加下列语句

AllowUsers support

这将只允许"support"帐号通过ssh登录系统,如果需要增加其他用户,可以使用空格
隔开。支持*、?通配符。

对于root帐号,单独有一个配置选项

PermitRootLogin yes

21.13 在FreeBSD 4.3-RELEASE上安装libpcap、libnet

21.14 如何使自己的BMP图象成为启动logo

21.15 UDMA ICRC error是什么意思

Q: 在console上出现错误信息"UDMA ICRC error writing... ...",什么意思

A: tt <warning3@nsfocus.com>

通常是使用了40线的IDE硬盘线,然而硬盘被设置成使用DMA模式,这种模式需要80线
硬盘线。也有可能是您的硬盘不支持DMA方式。解决方法有几种

1) 换用一根80线的IDE硬盘线(没干过)
2) 在CMOS BIOS中关闭对UDMA的支持
3) 在FreeBSD中关闭对UDMA的支持

vi /etc/sysctl.conf
hw.atamodes=pio,pio,pio,pio,

这样做,可能会降低硬盘速率。

21.16 Limiting closed port RST response什么意思

Q: console上出现"Limiting closed port RST response",什么意思

A: tt <warning3@nsfocus.com>

某些主机快速访问你的主机上一些没有开放的端口,你的主机正在回复RST报文,这
是正常反应。但FreeBSD内核限制了每秒钟回复RST报文的数量,以防止发生可能的
DoS攻击。例如,如果攻击者通过伪造源IP来向你的未开端口发送大量连接请求,就
可能诱使你的主机向该主机发送RST报文。这可能导致受害主机所在网络的带宽占用。
如果你不想看到上述信息,可以打开黑洞模式来停止响应RST报文。这也可以减缓远
程攻击者对你的主机的扫描速度。

# sysctl -w net.inet.tcp.blackhole=2
# sysctl -w net.inet.udp.blackhole=1

也可以在/etc/sysctl.conf中增加下列选项使黑洞模式每次启动后都生效

net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1

21.17 如何获取FreeBSD Kernel Source Code

Q: 没有装FreeBSD Kernel Source Code,但现在想在Windows下用Source Insight分
析内核源码,怎么办

A: 放入FreeBSD安装光盘,手动mount光驱,做如下操作

# dmesg | grep -i CDROM
acd0: CDROM <ATAPI-CD ROM-DRIVE-50MAX> at ata1-slave using PIO4
# ls -l /dev/acd0*
crw-r----- 2 root operator 117, 0 Nov 15 21:46 /dev/acd0a
crw-r----- 2 root operator 117, 2 Nov 15 21:46 /dev/acd0c
# mount -t cd9660 -r /dev/acd0a /cdrom
# cd /tmp
# cat /cdrom/src/ssys.[ab]? > FreeBSD_44R_ssys.tgz
# cat /cdrom/src/slib.[ab]? > FreeBSD_44R_slib.tgz
# cat /cdrom/src/slibexec.[ab]? > FreeBSD_44R_slibexec.tgz
# cat /cdrom/src/sgnu.[ab]? > FreeBSD_44R_sgnu.tgz
# cat /cdrom/src/srelease.[ab]? > FreeBSD_44R_srelease.tgz
# cat /cdrom/src/sbin.[ab]? > FreeBSD_44R_sbin.tgz
# cat /cdrom/src/ssbin.[ab]? > FreeBSD_44R_ssbin.tgz
# cat /cdrom/src/subin.[ab]? > FreeBSD_44R_subin.tgz
# cat /cdrom/src/susbin.[ab]? > FreeBSD_44R_susbin.tgz
# cat /cdrom/src/sbase.[ab]? > FreeBSD_44R_sbase.tgz
# cat /cdrom/src/sshare.[ab]? > FreeBSD_44R_sshare.tgz
# cat /cdrom/src/stools.[ab]? > FreeBSD_44R_stools.tgz
# cat /cdrom/src/sinclude.[ab]? > FreeBSD_44R_sinclude.tgz

这些FreeBSD*.tgz可以下载回Windows系统中,winzip打开即可。

21.18 /boot/defaults/loader.conf中的技巧

D: 小四 <scz@nsfocus.com> 2002-01-09 13:46

1) 启动时加载/kernel前的10s暂停如何调整

autoboot_delay="10" # Delay in seconds before autobooting

21.19 FreeBSD中sysctl可控内核参数

D: 小四 <scz@nsfocus.com> 2002-01-10 10:31

1) kern.ps_showallprocs

参看sys/kern/kern_proc.c,该值缺省是1,如果
sysctl -w kern.ps_showallprocs=0,则只有root用户才能看到所有进程(ps -aux),
其它用户不能看到非自己所有的进程。

2) kern.maxfiles
kern.maxfilesperproc

前者是整个系统所能打开的最大文件句柄数,后者是每个进程所能打开的最大文件句
柄数

3) kern.maxproc
kern.maxprocperuid

前者是系统所允许的最大进程数,后者是每个UID所能拥有的最大进程数

4) net.inet.icmp.bmcastecho

0 忽略广播、组播包
1 响应广播、组播包

5) net.inet.ip.forwarding

0 关闭IP转发
1 打开IP转发

6) net.inet.tcp.log_in_vain
net.inet.udp.log_in_vain

如果设置为1就会在日志中记录针对本机的端口扫描。

7) kern.ps_argsopen (FreeBSD 4.7-STABLE)

0 在ps的输出中不显示argv[]数组
1 在ps的输出中显示argv[]数组

21.20 x86/FreeBSD 4.3-RELEASE下GETIFADDRS(3)手册页

21.21 FreeBSD下如何访问显存

Q:

我在开发一个FreeBSD上的内核模块,需要读写VGA文本模式屏幕缓冲区,也就是物理
地址0xB8000。如果在MS-DOS下,当然可以直接访问这个地址,但现在我在FreeBSD内
核中,必须使用内核虚拟地址,我的问题是如何"正确地"转换物理地址到内核虚拟地
址。目前我用了如下技巧,0xC0000000 + 0xB8000,但这是不规范的。

A: "M. Warner Losh" <imp@village.org>

1) bus_alloc_resource()
2) bus_space_{read,write}_*

D: 小四 <scz@nsfocus.com>

应该也可以用mmap()通过/dev/mem映射0xB8000吧,参看"直接访问内存[显存]地址"

21.22 FreeBSD下如何为指定用户设定chroot的FTP环境

A: backend <backend@nsfocus.com> 2002-03-19 11:13

将该用户帐号添加到/etc/ftpchroot文件中,则该帐号FTP登录后被限制在其登录目
录下。如果希望限制用户组,则在组名前添加@字符并写入/etc/ftpchroot文件中。
修改后立即生效,不需要重启inetd进程。

# cat /etc/ftpchroot
badguy
@badgroup
#

21.23 如何利用FKLD动态增加一个新协议

Q: Marco Molteni <molter@tin.it>

在netinet/in_proto.c中有一个结构struct ipprotosw inetsw[],包含了各种IP协
议的入口。如果想增加一个新协议,就必须在这个结构数组中增加入口。我想知道能
否利用FKLD方式增加一个新协议

A: Brooks Davis <brooks@one-eyed-alien.net>

看看sys/net/if_gif.c,这里增加了一个新的IP协议,而且是可加载的。

21.24 修改/etc/mail/sendmail.cf关闭ident功能

Q: FreeBSD系统,本地/远程使用sendmail发信时缓慢,但TCP连接建立正常,为何

A: backend <backend@nsfocus.com>

用telnet连接25端口,发现在TCP连接建立后,第一条提示信息需要等待几秒才能出
现。利用netstat命令查看后发现sendmail进程在此之前连接客户端的113端口(即
ident),基本确定是这个查询客户端用户的操作产生延迟。修改
/etc/mail/sendmail.cf配置文件,关闭ident(RFC 1413)功能。

#O Timeout.ident=5s
O Timeout.ident=0s

21.25 FreeBSD下如何获取系统负载

A: Anthony Schneider <aschneid@mail.slc.edu>

可以用getloadavg(3)或者kvm_getloadavg(3)。进程信息可用kvm_getprocs(3)获取。

21.26 *BSD下如何屏敝远程登录时Copyright显示

A: 在自己主目录下生成一个文件.hushlogin

echo "ALLOWHUSH=NO" > .hushlogin

此外还有/etc/motd、/etc/issue

21.27 cvsup安装BASH

21.28 配置core dump

Q: 我在日志中看到

pid 36861 (ftpd), uid 29987: exited on signal 11

这是x86/FreeBSD 4.5-RELEASE-p3自带的ftpd。我使用-g参数重新编译了ftpd以
便调试

mkdir /var/coredump
chmod 1777 /var/coredump
sysctl kern.corefile=/var/coredump/%U.%N.%P.core (缺省为%N.core)

在/etc/login.conf中指定了

coredumpsize=unlimited

但是当ftpd崩溃时,依旧没有core dump,我不想等到4.6-RELEASE才解决这个问
题。

A: Mikko Tyolajarvi <mikko@dynas.se>

试试 sysctl kern.sugid_coredump=1 (缺省为0)

如果ftpd崩溃发生在用户登录完成之后,UID != EUID,必须做如上指定。遗憾的是,
这个设置并未解决前面的问题,不知还有什么地方限制了core dump产生?

D: Garrett Wollman <wollman@lcs.mit.edu>

设计FTP协议之初没有考虑到"特权端口"的问题,而在(服务器)主动模式下数据流绑
定源端口20/TCP,这需要root权限。如果ftpd不是以root身份启动,就无法完成这种
绑定。但又非始终需要root权限,所以有可能调用seteuid()、setreuid()临时放弃
特权。

某些ftpd的实现考虑到特权端口的问题,允许指定(服务器)主动模式下数据流是否绑
定20/TCP(违背RFC 959的要求),此时ftpd不必保持root权限。

至于控制流的21/TCP,多是来自inetd、xinetd,一旦TCP连接建立,就不需要root权
限了。

21.29 在OpenBSD 3.0上安装Gcc

21.30 在NetBSD 1.5.2上安装BASH

21.31 找不到何处启动了snmpd

21.32 FreeBSD远程root访问

A: 编辑/etc/ttys

# Pseudo terminals
ttyp0 none network off secure
ttyp1 none network off secure
ttyp2 none network off secure
ttyp3 none network off secure

22. Linux Kernel Programming

22.1 直接访问内存[显存]地址

Q: 现在在修改linux内核,希望能访问一段地址(其实是显存)。但发觉不能直接访问

A: Kongming <ymwei@263.net> (Luther <Luther@pku.edu> 整理)

通过/dev/mem设备文件和mmap系统调用,可以将线性地址描述的物理内存映射到进程
的地址空间,然后就可以直接访问这段内存了。

比如,标准VGA 16色模式的实模式地址是A000:0000,而线性地址则是A0000。设定显
存大小为0x10000,则可以如下操作

mem_fd = open( "/dev/mem", O_RDWR );
vga_mem = mmap( 0, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED,
mem_fd, 0xA0000 );
close( mem_fd );

然后直接对vga_mem进行访问,就可以了。当然,如果是操作VGA显卡,还要获得I/O
端口的访问权限,以便进行直接的I/O操作,用来设置模式/调色板/选择位面等等

在工控领域中还有一种常用的方法,用来在内核和应用程序之间高效传递数据:

1) 假定系统有64M物理内存,则可以通过lilo通知内核只使用63M,而保留1M物理内
存作为数据交换使用(使用 mem=63M 标记)。
2) 然后打开/dev/mem设备,并将63M开始的1M地址空间映射到进程的地址空间。

22.2 /proc可控内核参数

1) /proc/sys/net/ipv4/ip_forward

0 关闭IP转发
1 启动IP转发

# echo 1 > /proc/sys/net/ipv4/ip_forward

2) /proc/sys/net/ipv4/icmp_echo_ignore_all
/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

0 响应
1 忽略

23. Linux相关问题

23.0 以POST方式提交URL请求

A: san <san@nsfocus.com>

$ echo "uid=username&password=secret" | lynx -post_data http://www.url.com

A: tombkeeper <tombkeeper@nsfocus.com>

curl -v http://cygnus.tele.pw.edu.pl/cgi-bin/environ.pl-d xxx

23.1 RedHat 7.2远程root访问

Q: 缺省安装RedHat Linux 7.2后我想远程telnet、ftp访问之,以root身份

A: 2002-03-03 17:37

尽管从安全角度不建议如此做,但的确是可以实现的

1) vi /etc/securetty,增加类似行

pts/0
pts/1
pts/2
... ...

此后root可以远程telnet登录

2) vi /etc/ftpusers,注释或删除root帐号

3) vi /etc/ftpaccess,修改得到

allow-uid root
allow-gid root

此后root可以远程ftp登录

D: hhuu@smth 2003-06-10

如果从127.0.0.1以外的地址telnet访问,TCP连接被立刻切断的话,需要修改
/etc/hosts.allow文件。"cat /var/log/security"可以看到这样做的原因。

RedHat Linux 7.2缺省安装结束后,/etc/hosts.allow文件没有额外设置过。

23.2 TELNET/FTP连接耗时过长

Q: Redhat Linux 7.2 wu-ftpd,在LAN内时FTP连接正常,托管到电信(不在同一LAN)
后,FTP连接耗时过长,大约30s。

A: 木犀

有两种可能

1) 反向域名解析耗时,此时可在/etc/hosts增加相应client ip条目

2) RFC 931认证查询(113/TCP)

wu-ftpd支持RFC 931认证查询,大约10s才放弃。如果client位于NAT、Firewall
之后,server无法主动向client的113/TCP建立TCP连接,就必然等待10s超时。解
决办法有两种

a. 允许server主动向client的113/TCP建立TCP连接,此时不要求client上identd
真实存在,server发现client回送RST包便不再等待。

b. vi /etc/xinetd.d/wu-ftp

删除如下两行并重启xinetd

log_on_success += DURATION USERID
log_on_failure += USERID

D: flowbusily@bbs.apue.net 2002-10-10

我使用proftpd来做FTP Server,文中提到的反向域名解析与113/TCP认证在proftpd
里用如下两个选项来解决:

UseReverseDNS off
IdentLookups off

D: hek@smth 2003-04-08

配置服务器不要试图连接客户端的113/TCP

/etc/xinetd.d/telnet

log_on_failure += USERID

修改如下

log_on_failure += HOST

/etc/xinetd.d/wu-ftpd

log_on_success += DURATION USERID
log_on_failure += USERID

修改如下

log_on_success += DURATION HOST
log_on_failure += HOST

23.3 Debian/Linux中如何修改本机IP

A: 修改/etc/network/interfaces、/etc/hosts文件

--------------------------------------------------------------------------
# /etc/network/interfaces -- configuration file for ifup(8), ifdown(8)

# The loopback interface
auto lo
iface lo inet loopback

# The first network card - this entry was created during the Debian
# installation (network, broadcast and gateway are optional)
auto eth0
iface eth0 inet static
address 192.168.7.148
netmask 255.255.255.0
network 192.168.7.0
broadcast 192.168.7.255
gateway 192.168.7.254
--------------------------------------------------------------------------

23.4 如何确认是何种Unix Release

Q: "uname -a"看不出来是RedHat,如何确认是RedHat

A: $ cat /etc/redhat-release
Red Hat Linux release 7.2 (Enigma)

D: melonm@smth 2003-06-08

类似的操作有:

cat /etc/release (for solaris)
cat /etc/issue (for some others)
cat /etc/slackware-version (for slackware)

如果"uname -a"可以看出是何种Unix,就不必这样操作,所以Solaris用不着这样。

23.5 vi/insert状态下copy/paste时不回车、只换行

Q: 远程telnet/ssh登录,在vi/insert状态下copy/paste时不回车、只换行,结果显
示逞向屏幕右下角的阶梯状。多次在Linux中遇上该问题,如何解决。

A: 编辑~/.vimrc文件,放入如下两行内容:

set noautoindent
set nocindent

如果只是临时改动,可在vi的命令状态输入上述两条命令。还有一种可能,就是
汉学处理带来的问题,此时可修改环境变量:

ls -la /usr/lib/locale
export LANG=zh_CN.gbk

23.6 如何产生core dump

Q: 环境: Linux/pthread/g++

我的程序存在一个很难重现的BUG,它导致Segment fault,但未core dump。当我
用GDB调试该程序时,这个BUG"消失"了。我该如何定位BUG。

D: csmith@micromuse.com

试试pthread_kill_other_threads_np(),使之成为信号句柄:

--------------------------------------------------------------------------
struct sigaction sa = { 0 };

/*
* Set the handler for SEGV, BUS, ILL and ABRT to the Linux thread
* function below, to kill the threads so we can produce a core dump.
* It should be safe using pthread_kill_other_threads_np directly,
* as it takes no arguments (will ignore the signo) and returns void
* also.
*/
sa.sa_handler = pthread_kill_other_threads_np;
sa.sa_flags = SA_RESETHAND | SA_RESTART;

sigaction( SIGSEGV, &sa, NULL );
sigaction( SIGBUS, &sa, NULL );
sigaction( SIGILL, &sa, NULL );
sigaction( SIGABRT, &sa, NULL );
--------------------------------------------------------------------------

我们用这种办法在Linux上调试多线程程序。可能会丢失其它线程的信息,但聊胜于
无。

23.7 Socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错

Q: Socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错

socket() failed: Address family not supported by protocol

A: 重新配置、编译内核

CONFIG_PACKET=y

这是缺省设置。如果未使能该选项,就会得到上述错误信息。

24. Unix编程相关问题

24.0 如何知道fd是有效文件句柄

Q: 如果一个函数需要一个有效文件句柄(可以是pipe、fd、socket等等)做为形参,
有无办法判断其有效性。

A: Joe Halpin <jhalpin@nortelnetworks.com_.nospam>

我觉得最好是用fstat()函数进行判断,当fd不是一个有效文件句柄时,该函数返
回-1,并设置errno为EBADF。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

fcntl( fd, F_GETFD, 0 );

可能这是最快的办法,也最具有可移植性。在那些支持procfs的系统中,也可以
检查/proc/<pid>/fd/目录下的伪文件,比如SPARC/Solaris 8和x86/Linux
Kernel 2.4.7-10。而x86/FreeBSD 4.5-RELEASE中没有/proc/<pid>/fd/目录。显
然这种技术可移植性差了很多。

24.1 如何使代码段可写

Q: 如下演示程序试图对代码段进行写操作,缺省情况下必然失败,有何建议。

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/Linux RedHat/7.2 2.4.7-10(gcc 2.96/gas 2.11.90.0.8)
* : gcc -static -Wall -pipe -g -o src src.c
* -----------------------------------------------------------------------
*/
#include <stdio.h>

int main ( int argc, char * argv[] )
{
unsigned int *p;

p = ( unsigned int * )&main;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
*p = 0x4F46534E;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
return( 0 );
} /* end of main */
--------------------------------------------------------------------------

$ ./src
[0x080481E0] -> 0x83E58955
Segmentation fault (core dumped)
$

A: scz <scz@nsfocus.com>

无论是哪种Unix系统,总可以利用mprotect()设置PC附近的内存权限为rwx:

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/Linux RedHat/7.2 2.4.7-10(gcc 2.96/gas 2.11.90.0.8)
* : gcc -static -Wall -pipe -g -o src_other src_other.c
* -----------------------------------------------------------------------
*/
#include <stdio.h>
#include <sys/mman.h>

int main ( int argc, char * argv[] )
{
unsigned int *p = ( unsigned int * )( ( unsigned int )&main & 0xffffc000 );

if ( mprotect( p, 0x4000, 7 ) < 0 )
{
perror( "mprotect error" );
return( -1 );
}
p = ( unsigned int * )&main;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
*p = 0x4F46534E;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
return( 0 );
} /* end of main */
--------------------------------------------------------------------------

$ ./src_other
[0x080481E0] -> 0x83E58955
[0x080481E0] -> 0x4F46534E

修改静态文件中代码段的p_flags,从(PF_R | PF_X)改为(PF_R | PF_W | PF_X),这
样的ELF文件加载后,代码段缺省可写。下面是一个简单的跨平台可移植演示程序:

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/Linux RedHat_8 2.4.18-14
* : For x86/FreeBSD 4.5-RELEASE
* : For SPARC/Solaris 8
* : gcc -Wall -pipe -O3 -o codew codew.c
* -----------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PT_LOAD 1 /* Loadable program segment */
#define PF_X (1 << 0) /* Segment is executable */
#define PF_W (1 << 1) /* Segment is writable */
#define PF_R (1 << 2) /* Segment is readable */

/*
* The ELF file header. This appears at the start of every ELF file.
*/
struct ELF32EH
{
unsigned char e_ident[16]; /* Magic number and other info */
unsigned short int e_type; /* Object file type */
unsigned short int e_machine; /* Architecture */
unsigned int e_version; /* Object file version */
unsigned int e_entry; /* Entry point virtual address */
unsigned int e_phoff; /* Program header table file offset */
unsigned int e_shoff; /* Section header table file offset */
unsigned int e_flags; /* Processor-specific flags */
unsigned short int e_ehsize; /* ELF header size in bytes */
unsigned short int e_phentsize; /* Program header table entry size */
unsigned short int e_phnum; /* Program header table entry count */
unsigned short int e_shentsize; /* Section header table entry size */
unsigned short int e_shnum; /* Section header table entry count */
unsigned short int e_shstrndx; /* Section header string table index */
} __attribute__ ((packed));

struct ELF32PH
{
unsigned int p_type; /* Segment type */
unsigned int p_offset; /* Segment file offset */
unsigned int p_vaddr; /* Segment virtual address */
unsigned int p_paddr; /* Segment physical address */
unsigned int p_filesz; /* Segment size in file */
unsigned int p_memsz; /* Segment size in memory */
unsigned int p_flags; /* Segment flags */
unsigned int p_align; /* Segment alignment */
} __attribute__ ((packed));

int main ( int argc, char * argv[] )
{
struct ELF32EH eh;
struct ELF32PH ph;
unsigned short int e_phnum;
int fd = -1;

if ( argc != 2 )
{
fprintf( stderr, "Usage: %s <32-bit ELF file>/n", argv[0] );
return( EXIT_FAILURE );
}
if ( ( fd = open( argv[1], O_RDWR ) ) < 0 )
{
perror( "open error" );
return( EXIT_FAILURE );
}
if ( read( fd, &eh, sizeof( eh ) ) != sizeof( eh ) )
{
printf( "read eh error/n" );
goto main_0;
}
for ( e_phnum = 0; e_phnum < eh.e_phnum; e_phnum++ )
{
if ( lseek( fd, eh.e_phoff + e_phnum * sizeof( ph ), SEEK_SET ) < 0 )
{
perror( "lseek error" );
goto main_0;
}
if ( read( fd, &ph, sizeof( ph ) ) != sizeof( ph ) )
{
printf( "read ph error/n" );
goto main_0;
}
if ( ( ph.p_type == PT_LOAD ) && ( ( ph.p_flags & ( PF_R | PF_X ) ) ==
( PF_R | PF_X ) ) )
{
printf( "old ph.p_flags = 0x%08x/n", ph.p_flags );
ph.p_flags |= PF_W;
printf( "new ph.p_flags = 0x%08x/n", ph.p_flags );
lseek( fd, eh.e_phoff + e_phnum * sizeof( ph ), SEEK_SET );
if ( write( fd, &ph, sizeof( ph ) ) != sizeof( ph ) )
{
printf( "write ph error/n" );
goto main_0;
}
break;
}
} /* end of for */

main_0:

close( fd );
fd = -1;
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

下面在SPARC/Solaris 8上测试效果:

$ ./src
[0x0001080C] -> 0x9DE3BF88
段错误 (core dumped)
$ elfdump -p src
程序头[2]:
p_vaddr: 0x10000 p_flags: [ PF_X PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x934 p_memsz: 0x934
p_offset: 0 p_align: 0x10000
$ ./codew src
old ph.p_flags = 0x00000005
new ph.p_flags = 0x00000007
$ elfdump -p src
程序头[2]:
p_vaddr: 0x10000 p_flags: [ PF_X PF_W PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x934 p_memsz: 0x934
p_offset: 0 p_align: 0x10000
$ ./src
[0x0001080C] -> 0x9DE3BF88
[0x0001080C] -> 0x4F46534E

对于Linux、FreeBSD有readelf工具可用:

$ readelf -l src
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz *** Align
LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 R E 0x1000
$ ./codew src
old ph.p_flags = 0x00000005
new ph.p_flags = 0x00000007
$ readelf -l src
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz *** Align
LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 RWE 0x1000
$

用"objdump -x src | more"也能看到这些信息。

"law@bbs.apue.net"曾经建议过这样的命令:

$ ./src
[0x08048494] -> 0x83E58955
Bus error (core dumped)
$ objdump -h src
Sections:
Idx Name Size VMA LMA File off Algn
8 .text 00000190 08048388 08048388 00000388 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
$ objcopy --set-section-flags .text=CONTENTS,ALLOC,LOAD,CODE src dst
$ objdump -h dst
Idx Name Size VMA LMA File off Algn
8 .text 00000190 08048388 08048388 00000388 2**2
CONTENTS, ALLOC, LOAD, CODE
$ ./dst
[0x08048494] -> 0x83E58955
Bus error (core dumped)
$

这条objcopy命令修改了.text的sh_flags,而不是全局的p_flags,去掉READONLY也
未能使得代码段缺省可写。

"watercloud@nsfocus.com"建议过另一种邪门办法,下面在SPARC/Solaris 8上演示:

$ gcc -Wall -S -o src.s src.c

编辑src.s文件,将main()所在的节名由.text改成.data,继续编译:

$ gcc -static -Wall -pipe -g -o src src.s
$ ./src
[0x0004E770] -> 0x9DE3BF88
[0x0004E770] -> 0x4F46534E
$ elfdump -p src
程序头[0]:
p_vaddr: 0x10078 p_flags: [ PF_X PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x2e67e p_memsz: 0x2e67e
p_offset: 0x78 p_align: 0x10000
程序头[1]:
p_vaddr: 0x4e6f8 p_flags: [ PF_X PF_W PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x1a31 p_memsz: 0x2640
p_offset: 0x2e6f8 p_align: 0x10000
$

24.2 建议性文件锁与强制性文件锁

D: 某些Unix系统(SVR4)支持强制性文件锁。假设一个文件被设置过强制性文件锁,
此时操作系统内核将阻止其它进程对该文件进行creat、open、read、write操作,
errno设置成EAGAIN。因此强制性文件锁比较危险,如果对某个比较重要的系统文
件设置过强制性文件锁又忘记释放,有可能导致系统崩溃。设置强制性文件锁的
方式比较特别:

chmod g+s <filename>
chmod g-x <filename>

这是形象的表示,实际编程中应该通过chmod()函数一步完成。不能对目录、可执
行文件设置强制性锁。

24.3 如何编写daemon程序

Q: 在FreeBSD下用"ps auxw"查看进程列表时,注意到某些进程没有控制终端,也就
是说TT列显示??。知道这是所谓的daemon进程,如果我想自己编写这样的程序,
该如何做。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

这个回答来自著名的<<Unix Programming FAQ ver 1.37>>,由Andrew Gierth负责维
护,其它细节请参看原文1.7小节。

通常将一个不与任何终端相关联的后台进程定义为daemon进程。下面是通常所需的七
个步骤:

a. fork()之后父进程退出。子进程确保不是process group leader,这是成功调用
setsid()所要求的。

b. setsid(),创建新的session和process group,成为其leader,并脱离控制终端。

c. 再次fork()之后父进程退出,子进程确保不是session leader,将永远不会重获
控制终端。这是SVR4的特性所致。

d. chdir( "/" ),减少管理员卸载(unmount)文件系统时可能遇上的麻烦。这一步可
选,也可chdir()到其它目录。

e. umask( 0 ),使当前进程对自己所写文件拥有完全控制权,避免继承的umask()设
置带来困挠。这一步可选。

f. 关闭0、1、2三个句柄。许多daemon程序用sysconf()获取_SC_OPEN_MAX,并在一
个偱环中关闭所有可能打开的文件句柄。目的在于释放不必要的系统资源,它们
是有限资源。

g. 出于安全以及健壮性考虑,即使当前进程不使用stdin、stdout、stderr,也应重
新打开0、1、2三个句柄,使之对应/dev/null。当然,你也可以根据需要使之对
应不同的(伪)文件。总之,保持0、1、2三个句柄呈打开状态,并使之指向无害文
件。

D: scz <scz@nsfocus.com>

以FreeBSD 4.5-RELEASE为例进行讨论。

注意,存在与终端相关联的后台进程,比如在支持作业控制的bash上以&符结尾启动
的进程。当用"ps auxw"查看时,这种后台进程的TT列不为??。用"ps -p pid -jfl"
查看这种后台进程,可以看到其PGID与父进程的PGID不同,属于另外一个进程组。支
持作业控制的现代shell对&符的解释一般都是fork/setpgid。前台进程组、后台进程
组是终端的属性,不是进程本身的属性,没有控制终端的进程无所谓前台、后台,一
定要算就都算是后台进程。

非作业控制型的shell对&符的解释一般只是fork,而没有setpgid,这样启动的进程
与shell属于同一进程组。后面的讨论都假设使用支持作业控制的现代shell。

当在控制终端上按下Ctrl-C,终端驱动程序产生SIGINT信号(可用stty设置)并分发至
前台进程组的所有进程。

APUE 10.2中提到,当session leader终止时,系统会向该session前台进程组中所有
进程分发SIGHUP信号。我的疑问是,如果某session没有控制终端,也就没有所谓前
台进程组,当session leader终止时,系统会向该session中所有进程分发SIGHUP信
号吗。UNP 12.4的例子正是这种情形,可是Stevens没有在其它地方进一步阐述,也
永远不可能得到他本人的解释了。

APUE 13.3所给的daemon_init()与UNP 12.4所给不同,没有做二次fork()。因为二次
fork()只是SVR4的要求。从最广泛兼容角度出发,如果daemon进程企图打开一个(伪)
终端设备,无论是否二次fork()过,open()时都应该指定O_NOCTTY。由于daemon程序
是自己完全可控的,将来是否会打开终端是已知的,如果确认将来不会打开终端,就
完全不必考虑重获控制终端的问题,换句话说,二次fork()很大程度上是不必要的。

关于Andrew Gierth所提第七步骤,1987年Henry Spencer在setuid(7)手册页中做了
相关建议,1991年,在comp news上有人重贴了这份文档。1992年Richard Stevens建
议daemon进程应该关闭所有不必要的文件句柄,并将stdin、stdout、stderr指向
/dev/null。参看<<x86/FreeBSD 4.5-RELEASE IO Smash及S/Key机制分析>>。第七步
骤严格意义上来说,不是可选的,而是必须的。

参看<<19.0 如何将stdin、stdout、stderr重定向到/dev/null>>。

A: W. Richard Stevens

一般我会使用类似daemon_init()这样的函数,使当前进程成为daemon进程。

--------------------------------------------------------------------------
static void daemon_init ( const char *workdir, mode_t mask )
{
int i, j;

/*
* change working directory, this step is optional
*/
chdir( "/tmp" );
if ( 0 != Fork() )
{
/*
* parent terminates
*/
exit( EXIT_SUCCESS );
}
/*
* first child continues
*
* become session leader
*/
setsid();
Signal( SIGHUP, SIG_IGN );
if ( 0 != Fork() )
{
/*
* first child terminates
*/
exit( EXIT_SUCCESS );
}
/*
* second child continues
*
* change working directory, chdir( "/" )
*/
chdir( workdir );
/*
* clear our file mode creation mask, umask( 0 )
*/
umask( mask );
j = Open( "/dev/null", O_RDWR );
Dup2( j, 0 );
Dup2( j, 1 );
Dup2( j, 2 );
j = getdtablesize();
for ( i = 3; i < j; i++ )
{
close( i );
}
return;
} /* end of daemon_init */
--------------------------------------------------------------------------

调用setsid(),如果成功,导致三个结果:

a. 创建一个新的session,当前进程成为session leader,也是新session中的惟一
进程。

b. 当前进程成为一个新进程组的组长(process group leader)。

c. 如果当前进程以前有一个控制终端,现在将脱离这个控制终端。

对于SVR4,一个session leader调用open()打开一个(伪)终端设备,如果这个终端不
是其它会话的控制终端,而open()时又未指定O_NOCTTY,则这个终端成为当前会话的
控制终端。第二次fork()后,孙子进程将确保不是session leader。于是以后不会再
有任何控制终端,彻底脱离。

必须在第二次fork()之前显式忽略SIGHUP信号。孙子进程将继承子进程所设置的信号
句柄。Stevens是这样解释的,当session leader终止时,系统会向该session中所有
进程分发SIGHUP信号。即这里的子进程终止时,系统会向孙子进程分发SIGHHUP信号。
前面有关于这个问题的讨论。

getdtablesize()返回的也就是sysconf( _SC_OPEN_MAX )返回的值。

D: scz <scz@nsfocus.com>

以FreeBSD 4.5-RELEASE为例进行讨论。

做为Guru of the Unix gurus,Andrew Gierth与Richard Stevens在各类文档或书籍
中对"进程"进行了相当广泛、深入的解释,其中可能引发困惑的一个问题是,父子进
程关系与信号分发的关系。

有相当多的人认为父进程终止时,子进程应该收到一个SIGHUP信号。即使熟练的Unix
程序员参与某些讨论时,也可能忘记几分钟前TA还在fork(),并立即让父进程退出的
事实。一般来说,有两种典型的与SIGHUP信号相关的情形。

假设某session有控制终端,当session leader终止时,系统会向该session前台进程
组中所有进程分发SIGHUP信号。

如果某进程组中有一个进程,其父进程属于同一会话(session)的另一个进程组,则
该进程组不是"孤儿进程组",反之该进程组称为"孤儿进程组"。

APUE 9.10指出,当某进程的终止导致一个新的"孤儿进程组"产生,系统会向这个新
的"孤儿进程组"中处于"停止"状态的每个进程分发SIGHUP信号,然后分发SIGCONT信
号。那些未处于"停止"状态的进程不会收到这两个信号。

启动"tcpdump -i lnc0 udp &",此时tcpdump成为后台进程组成员。退出当前shell,
此时tcpdump成为孤儿进程组成员,但它处于"运行"状态。重新登录后会发现该进程
仍然存在,它不是daemon进程,TT列不为??。它没有收到SIGHUP信号,手动kill -1
是可以杀掉它的。

启动"nohup tcpdump -i lnc0 udp",此时tcpdump仍为前台进程组成员。从另一shell
执行"kill -1"杀掉前一shell,此时tcpdump成为孤儿进程组成员。有SIGHUP信号分
发到tcpdump,因为session leader终止了。nohup确保tcpdump继续运行。对比没有
使用nohup时的情形。

有一个bindshell,它只是简单fork()了一次,父进程立即退出。并未处理SIGHUP信
号,也未调用setsid()。它已经达到目的了。fork()之后产生一个后台孤儿进程组,
并未脱离控制终端,但再也不会有SIGHUP信号分发到bindshell。前述两种情形都不
会出现。在控制终端上按Ctrl-C产生的SIGINT信号不会分发到后台进程组。一般入侵
中要的就是这个效果,并不需要复杂的daemon_init()。

还有一种情形,简单fork()一次,父进程调用setpgid()使子进程自己成为进程组长,
然后父进程退出。这只是确保产生后台孤儿进程组,setpgid()不是必须的。子进程
仍然过继给init进程。

两位先生给出的daemon化步骤考虑得相当周全。但更多的入侵者、临时跳板工具并不
需要daemon化,最省事的办法就是fork()一次。最后再强调一次,脱离控制终端、彻
底脱离控制终端与不受SIGHUP信号影响是两回事,绝大多数时候要的只是后者的效果。

此外,Linux可能在某些细节上与上述讨论有出入,但最后的结论一样,最省事的办
法就是fork()一次。

个人推荐严肃的Unix/C程序员在需要这类效果时,统一使用daemon_init(),并捕捉
相关信号。

D: lskuangren@bbs.apue.net 2003-07-11

FreeBSD和Linux直接提供了一个函数,DAEMON(3)

--------------------------------------------------------------------------
DAEMON(3) FreeBSD库函数手册 DAEMON(3)

名字

daemon - 在后台运行程序

标准C库(libc, -lc)

语法

#include <stdlib.h>

int daemon ( int nochdir, int noclose );

描述

daemon()用于脱离控制终端、转入后台运行程序(守护进程)。

如果第一形参nochdir为零,daemon()最终执行chdir( "/" )。

如果第二形参noclose为零,daemon()最终将stdin、stdout、stderr重定向到
/dev/null。

错误

失败时返回-1,并设置errno,errno的值与fork(2)、setsid(2)的情形一致。

参看

fork(2), setsid(2)

历史

4.4BSD首次引入了daemon()。
--------------------------------------------------------------------------

从man手册可以看出daemon()都做了些什么。在清楚自己到底需要何种效果的前提下,
可以不使用复杂的daemon_init()而直接使用daemon()。

AIX、Solaris未直接提供daemon(),如编写最广泛兼容程序,应避免使用daemon()。

24.4 将编译、链接过程分开

Q: 如下程序直接用gcc编译、链接无问题。但是将编译、链接过程分开时,失败。

$ gcc -Wall -pipe -O3 -c -o helloworld.o helloworld.c
$ ld -o helloworld helloworld.o
ld: warning: cannot find entry symbol _start; defaulting to 08048074

--------------------------------------------------------------------------
/*
* For x86/Linux 2.4.7-10/RedHat 7.2(gcc 2.96/gas 2.11.90.0.8)
* gcc -Wall -pipe -O3 -o helloworld helloworld.c
*/
#include <stdio.h>
#include <stdlib.h>

int main ( int argc, char * argv[] )
{
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

A: scz <scz@nsfocus.com>

$ gcc -v -Wall -pipe -O3 -o helloworld helloworld.c &> gcc.txt
$ more gcc.txt

gcc.txt的内容真实反映了整个编译、链接过程,可简化成如下三个步骤:

$ gcc -Wall -pipe -O3 -S -o helloworld.s helloworld.c
$ as -Qy -o helloworld.o helloworld.s
$ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o helloworld helloworld.o /
/usr/lib/crt1.o /
/usr/lib/crti.o /
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o /
-lc /
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /
/usr/lib/crtn.o

25. AIX相关问题

25.0 如何查看AIX版本号

Q: "uname -a"显示如下:

AIX aix4 3 4 001381144C00

可我想直接了当地看到AIX版本号

A: oslevel

4.3.3.0

25.1 如何在AIX命令行上修改IP地址

Q: 在/etc下用find搜索原来所配置的IP地址,结果只在/etc/hosts文件中找到一个
相关匹配,其它匹配是日志文件一类的。

find /etc -name "*" -type f | xargs grep <original ip>

于是我假设AIX跟Solaris一样只依赖/etc/hosts文件设置IP地址,修改该文件后
重启,发现新IP未生效,旧IP也不能正常工作。

A: 1) 正规命令行修改步骤如下:

smit tcpip
Further Configuration
Network Interfaces
Network Interface Selection
Change / Show Characteristics of a Network Interface
en0 Standard Ethernet Network Interface

2) 也可以用ifconfig直接修改IP地址,并放入启动脚本/etc/rc.net中。

ifconfig en0 192.168.7.250 netmask 255.255.255.0 broadcast 192.168.7.255 up

25.2 如何查看RS/6000物理内存大小

A: # lsattr -El sys0 -a realmem
realmem 262144 Amount of usable physical memory in Kbytes False

或者

# lsdev -Cc memory
mem0 Available 00-00 Memory
L2cache0 Available 00-00 L2 Cache
# lsattr -El mem0
size 256 Total amount of physical memory in Mbytes False
goodsize 256 Amount of usable physical memory in Mbytes False

25.3 AIX 4.3.3中"ls a*"不正常

Q: 某目录下简单执行ls,正常。执行"ls a*",报错:

ksh: /usr/bin/ls: 0403-027 The parameter list is too long.

原因是/usr/include/sys/limits.h里面ARG_MAX定义成24576。AIX 5可以修改这
个限制,AIX 4.3.3没有办法修改。

怎样达到原始目的,只显示当前目录(不包含其子目录)中以a开头的文件。

A: 至少有两种办法

#! /bin/sh
for file in `ls` ; do
if test -f $file ; then
if `/usr/bin/echo ${file} | /usr/bin/grep "^a.*" >/dev/null 2>&1` ; then
echo "${file}"
fi
fi
done

或者

ls -F | grep "^a.*[^/]$"

25.4 AIX多线程编程与errno全局变量

Q: AIX多线程编程中使用了全局变量errno,有什么需要注意的地方。

A: wayman@smth

使用gcc -D_THREAD_SAFE,此时errno是每个线程相关的,不再是全局变量。当正确地
包含<errno.h>之后,errno被重新定义过:

#if defined(_THREAD_SAFE) || defined(_THREAD_SAFE_ERRNO)
/*
* Per thread errno is provided by the threads provider. Both the extern
* int and the per thread value must be maintained by the threads library.
*/
#define errno (*_Errno())

#endif

extern int errno;

函数_Errno()返回一个指针,指向一个线程相关整数。注意,你仍然可以使用&errno。

在/usr/include/*.h中grep查看一下,会发现如果指定了-D_THREAD_SAFE,就不必再
指定-D_REENTRANT以及-D_THREAD_SAFE_ERRNO。

--

ytisrevinu iaknan gnikcah

※ 来源:·我爱南开站 nkbbs.org ·Web[FROM: 221.196.56.51]

作者:飞猫头衔:版主发布时间:  2004-12-23 18:03:20

第5楼: 
19.2 telnet时如何关闭本地回显

19.3 如何清空stdin的缓冲

A: law@bbs.apue.net

stdin->_IO_read_ptr = stdin->_IO_read_end;

不过这个办法实在不怎么样。一是只对glibc有效,不可移植。二是违背流的思想,
老老实实用fgets()好了。

A: scz <scz@nsfocus.com> 2003-01-12 19:16

tcflush( STDIN_FILENO, TCIFLUSH );

19.4 Linux Console下一按错键就叫,怎么关

A: windtear@bbs.tsinghua.edu.cn Linux版

有个1050110 背一下就可以了

echo -e "//33[10;50]//33[11;0]"

10 50 11 0

放到那些登录自启动脚本里

A: Sidos@smth.org

如果想在X下也没有这个声音,在/etc/inputrc里加上

set bell-style none

Q: 输完命令后是没声了,可从KDE回来之后又有了,请问能彻底关掉吗

A: TheCool@bbs.tsinghua.edu.cn Linux版

setterm -blength 0 -bfreq 0

D: 对于FreeBSD,如果你不想在XWindow下听到beep,可以"xset b off"

19.5 从stdin立即获取按键

Q: Linux/C编程环境,从标准输入stdin读取内容时,有无办法立即获取按键,而不
必等待换行。事实上我需要MS-DOS下的kbhit()、getch()函数。

有些人总是建议进入(n)curses环境,可我不想使用这种多此一举的技术。

A: Floyd Davidson <floyd@ptialaska.net>

我们就作者所提供的原始代码进行了一些移植、修正,手头系统有限,未做更广泛的
可移植性测试。

--------------------------------------------------------------------------
/*
* For x86/Linux Kernel 2.4.7-10
* gcc -DLinux -Wall -pipe -O3 -o input_demo input_demo.c
*
* For x86/FreeBSD 4.5-RELEASE
* gcc -DFreeBSD -Wall -pipe -O3 -o input_demo input_demo.c
*
* For SPARC/Solaris 8
* gcc -DSolaris -Wall -pipe -O3 -o input_demo input_demo.c
*
* kbhit() -- a keyboard lookahead monitor
* getch() -- a blocking single character input from stdin
*
* Plus a demo main() to illustrate usage.
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>

#ifdef Solaris
#include <sys/filio.h>
#endif

#undef TERMIOSECHO
#define TERMIOSFLUSH

/*
* getch() -- a blocking single character input from stdin
*
* Returns a character, or -1 if an input error occurs
*
* Conditionals allow compiling with or without echoing of the input
* characters, and with or without flushing pre-existing buffered input
* before blocking.
*/
static int getch ( void )
{
struct termios old_termios, new_termios;
int error;
char c;

fflush( stdout );
tcgetattr( 0, &old_termios );
new_termios = old_termios;
/*
* raw mode, line settings
*/
new_termios.c_lflag &= ~ICANON;
#ifdef TERMIOSECHO
/*
* enable echoing the char as it is typed
*/
new_termios.c_lflag |= ECHO;
#else
/*
* disable echoing the char as it is typed
*/
new_termios.c_lflag &= ~ECHO;
#endif
#ifdef TERMIOSFLUSH
/*
* use this to flush the input buffer before blocking for new input
*/
#define OPTIONAL_ACTIONS TCSAFLUSH
#else
/*
* use this to return a char from the current input buffer, or block
* if no input is waiting
*/
#define OPTIONAL_ACTIONS TCSANOW
#endif
/*
* minimum chars to wait for
*/
new_termios.c_cc[VMIN] = 1;
/*
* minimum wait time, 1 * 0.10s
*/
new_termios.c_cc[VTIME] = 1;
error = tcsetattr( 0, OPTIONAL_ACTIONS, &new_termios );
if ( 0 == error )
{
/*
* get char from stdin
*/
error = read( 0, &c, 1 );
}
/*
* restore old settings
*/
error += tcsetattr( 0, OPTIONAL_ACTIONS, &old_termios );
return( error == 1 ? ( int )c : -1 );
} /* end of getch */

/*
* kbhit() -- a keyboard lookahead monitor
*
* returns the number of characters available to read
*/
static int kbhit ( void )
{
struct timeval tv;
struct termios old_termios, new_termios;
int error;
int count = 0;

tcgetattr( 0, &old_termios );
new_termios = old_termios;
/*
* raw mode
*/
new_termios.c_lflag &= ~ICANON;
/*
* disable echoing the char as it is typed
*/
new_termios.c_lflag &= ~ECHO;
/*
* minimum chars to wait for
*/
new_termios.c_cc[VMIN] = 1;
/*
* minimum wait time, 1 * 0.10s
*/
new_termios.c_cc[VTIME] = 1;
error = tcsetattr( 0, TCSANOW, &new_termios );
tv.tv_sec = 0;
tv.tv_usec = 100;
/*
* insert a minimal delay
*/
select( 1, NULL, NULL, NULL, &tv );
error += ioctl( 0, FIONREAD, &count );
error += tcsetattr( 0, TCSANOW, &old_termios );
return( error == 0 ? count : -1 );
} /* end of kbhit */

int main ( int argc, char * argv[] )
{
struct termios old_termios, new_termios;
int count;
int c;

tcgetattr( 0, &old_termios );
printf( "You must enter 10 characters to get this program to continue:" );
fflush( stdout );
/*
* collect 10 characters
*/
for ( count = kbhit(); count < 10; count = kbhit() )
{
if ( -1 == count )
{
return( EXIT_FAILURE );
}
}
new_termios = old_termios;
/*
* disable echoing of further input
*/
new_termios.c_lflag &= ~ECHO;
tcsetattr( 0, TCSANOW, &new_termios );
printf( "/nStop, now type <Enter> to continue" );
fflush( stdout );
c = getchar();
/*
* enable echoing of further input
*/
tcsetattr( 0, TCSANOW, &old_termios );
printf( "/nThe first five characters are: [" );
/*
* print a few chars
*/
for ( count = 0; count < 4; count++ )
{
printf( "%c", ( char )c );
c = getchar();
}
printf( "%c]/n/n", ( char )c );
printf( "****** Demo Menu ******/n/n" );
printf( "Option Action/n" );
printf( " A Action_A/n" );
printf( " B Action_B/n" );
printf( " C Action_C/n" );
printf( " Q Exit/n/n" );
printf( "Enter your choice: [ ]/b/b" );
fflush( stdout );
/*
* note that calling getch() will flush remaining buffered input
*/
switch ( c = getch() )
{
case 'a':
case 'A':
printf( "%c/nAction_A/n", ( char )toupper( ( int )c ) );
break;
case 'b':
case 'B':
printf( "%c/nAction_B/n", ( char )toupper( ( int )c ) );
break;
case 'c':
case 'C':
printf( "%c/nAction_C/n", ( char )toupper( ( int )c ) );
break;
case 'q':
case 'Q':
printf( "%c/nExit/n", ( char )toupper( ( int )c ) );
break;
default:
printf( "%c/n", ( char )toupper( ( int )c ) );
break;
}
tcsetattr( 0, TCSANOW, &old_termios );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

D: 小四 <scz@nsfocus.com>

c_cc[VTIME]以0.10秒为单位指定一个字节间的读取超时,所对应的计时器在接收到
第一个字节之后才启动,因此c_cc[VMIN]为1的时候,c_cc[VTIME]是多少都无所谓。
关于这方面的详细讨论参看APUE 11.11小节。

input_demo.c:main()中第一个getchar()调用,其本意是在"规范模式"下阻塞,等待
一个换行。SPARC/Solaris 8下语义与x86/Linux Kernel 2.4.7-10、x86/FreeBSD
4.5-RELEASE不同,在此处未能阻塞住,我怀疑是该版本实现上的一个BUG。

19.6 如何屏蔽Ctrl-D

Q: Ctrl-C产生SIGINT信号,Ctrl-D产生什么信号。我想屏蔽Ctrl-D。

A: W. Richard Stevens

参看APUE 11.3小节的讨论。

Ctrl-C并不总是固定产生SIGINT信号,只能说通常如此。stty -a命令往往可以看到

intr = ^C; eof = ^D;

由当前终端属性决定,而这可以编程改变,或者就用stty命令设置。Ctrl-D通常导致
阻塞读操作返回EOF,并不产生任何信号,除非刻意设置过。

顺便说一句,为了指示文件结束,需要在"新行行首"输入EOF(通常是Ctrl-D)。

--------------------------------------------------------------------------
/*
* For x86/Linux Kernel 2.4.7-10
* x86/FreeBSD 4.5-RELEASE
* SPARC/Solaris 8
* gcc -Wall -pipe -O3 -o ctrl_cd ctrl_cd.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>

int main ( int argc, char * argv[] )
{
struct termios term;
long vdisable = 0;
char buf[80];

if ( isatty( STDIN_FILENO ) == 0 )
{
fprintf( stderr, "standard input is not a terminal device/n" );
return( EXIT_FAILURE );
}
vdisable = fpathconf( STDIN_FILENO, _PC_VDISABLE );
if ( vdisable < 0 )
{
perror( "fpathconf( STDIN_FILENO, _PC_VDISABLE ) error" );
return( EXIT_FAILURE );
}
if ( tcgetattr( STDIN_FILENO, &term ) < 0 )
{
perror( "tcgetattr( STDIN_FILENO, &term ) error" );
return( EXIT_FAILURE );
}
/*
* disable INTR character
*/
term.c_cc[VINTR] = vdisable;
/*
* EOF is Ctrl-B
*/
term.c_cc[VEOF] = 2;
if ( tcsetattr( STDIN_FILENO, TCSAFLUSH, &term ) < 0 )
{
perror( "tcsetattr( STDIN_FILENO, TCSAFLUSH, &term ) error" );
return( EXIT_FAILURE );
}
while ( fgets( buf, sizeof( buf ), stdin ) != NULL );
if ( feof( stdin ) != 0 )
{
printf( "fgets() found EOF/n" );
}
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

由于我们修改了c_cc[VINTR]、c_cc[VEOF]的标准设置,在测试ctrl_cd.c时,Ctrl-C
与Ctrl-D不再有效,需要用Ctrl-B输入EOF来结束运行。

ctrl_cd.c中没有恢复c_cc[VINTR]、c_cc[VEOF]的标准设置,可以简单利用stty命令
恢复:

stty intr ^C
stty eof ^D

这里^C用Ctrl-v、Ctrl-c输入,^D用Ctrl-v、Ctrl-d输入。

20. shell script问题

20.0 不用临时文件完成字符串替换

20.1 如何获取一个字符串的长度

A: Andrei Ivanov <iva@racoon.riga.lv>

expr `echo $string | wc -c` - 1

echo $string | awk '{ print length( $0 ); }'

/usr/ucb/expr length "$string"

expr "$string" : ".*"

echo "$string" | sed 's/./1+/g;s/+/ /;s/$/p/' | dc

A: http://www.linuxforum.net

假设是bash

$ string='1234567890'
$ echo ${#string}
10
$

20.2 读超时自动使用缺省值

Q: shell script编程,不介入expect、perl、tcl等类似工具。读等待60秒,超时则
自动使用缺省值。可以使用系统缺省外部命令,要求能广泛移植在常用Unix平台

A: CERNET 华中地区网络中心 PUE(UNIX环境程序设计)版 lookout

参看comp.unix.shell新闻组,下面以SPARC/Solaris 2.6为例

--------------------------------------------------------------------------
#! /sbin/sh
stty -icanon min 0 time 255
while true
do
/usr/bin/echo "Press a key or press ENTER to exit:/c"
read key
if [ "$key" = "" ] ; then
echo "/nYou press Enter or timeout"
break
else
echo "You press the key $key"
fi
done
stty sane
--------------------------------------------------------------------------

D: scz <scz@nsfocus.com>

参看termio(7I)、stty(1)手册页了解更多关于"非规范模式"的信息,下面这个C程序
揭示了上述shell script的本质

--------------------------------------------------------------------------
/*
* For x86/Linux、x86/FreeBSD、SPARC/Solaris
* gcc -Wall -pipe -O3 -o keypress keypress.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>

static struct termios originalTermParam;

static void set_keypress ( void )
{
struct termios currentTermParam;

tcgetattr( 0, &originalTermParam );
memcpy( ¤tTermParam, &originalTermParam, sizeof( struct termios ) );

/*
* Disable canonical mode, and set buffer size to 1 byte
*/
currentTermParam.c_lflag &= ~ICANON; /* 不设置则允许最快速的读取字符 */
currentTermParam.c_lflag &= ~ECHO; /* 不回显 */
/*
* 超时设置,255 * 0.10s,单位是0.10s
* 这里是一个字节,所以最大值为255
*/
currentTermParam.c_cc[ VTIME ] = 255;
/*
* 无阻塞输入,CPU占用率很高
* 如果该值为1,表示至少等待输入一个字符,阻塞
*/
currentTermParam.c_cc[VMIN] = 0;

tcsetattr( 0, TCSANOW, ¤tTermParam );
return;
} /* end of set_keypress */

void reset_keypress ( void )
{
tcsetattr( 0, TCSANOW, &originalTermParam );
return;
} /* end of reset_keypress */

int main ( void )
{
puts( "main start" );
set_keypress();
while ( 1 )
{
if ( getchar() == ( int )'q' )
{
break;
}
}
reset_keypress();
puts( "main stop" );
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

D: 小四 <scz@nsfocus.com> 2001-07-04 13:12

下面以SPARC/Solaris 2.6为例,提供一个GetYesOrNo()函数,第一形参指定超时缺
省返回值,第二形参指定超时时限(以0.10秒为单位),后续形参指定提示信息。

--------------------------------------------------------------------------
#! /bin/sh

GetYesOrNo ()
{
# 第一形参指定超时缺省返回值
case ${1} in
0 )
EXITCODE=${1};;
* )
# 非零皆为1
EXITCODE=1;;
esac
# 第二形参指定超时时限(以0.10秒为单位)
if [ ${2} -le 255 -a ${2} -ge 0 ] ; then
TIMEOUT=${2}
else
# 缺省超时时限5秒
TIMEOUT=50
fi
shift 2
SAVETERM=`stty -g`
# 设置xxx秒后读超时,非规范模式
stty -icanon min 0 time ${TIMEOUT}
# Solaris下,sh的内部命令echo支持/c这种用法
while echo "$* (Y/N) ?/c" >&2
do
read YesOrNo
case "$YesOrNo" in
[yY] )
EXITCODE=0
break;;
[nN] )
EXITCODE=1
break;;
"" )
# 超时或者选择使用缺省值
break;;
* )
echo "/nPlease enter Y(y) or N(n) ... ..." >&2;;
esac
done
# stty sane
stty ${SAVETERM}
echo ""
return ${EXITCODE}
}

GetYesOrNo 0 50 "Please select"
exit ${?}
--------------------------------------------------------------------------

20.3 如何删除空行、空白符组成的行

20.4 BASH中如何得到一个字符串的子串

20.5 shell script中如何关闭stdout

20.6 如何将一个文本文件开始的N行删除

20.7 以字符串(非单个字符)为分隔的析取

20.8 使用tr命令加密文件

A: 水木清华 TheCool

著名的 rot13 密码, 通过把字母移动13个位置实现对文本的加密

tr "[a-m][n-z][A-M][N-Z]" "[n-z][a-m][N-Z][A-M]" < message > newmessage

然后可以用同样的命令进行解密

tr "[a-m][n-z][A-M][N-Z]" "[n-z][a-m][N-Z][A-M]" < newmessage > message

20.9 有哪些命令用于查找定位

A: 小四

type -a telnet
whereis telnet
which telnet
whatis telnet <=> man -k telnet

20.10 非递归删除目录树

A: 小四 <scz@nsfocus.com>

先来看一个显示目录树的经典shell script

--------------------------------------------------------------------------
#! /bin/sh
# Author: Kenneth H. Rosen
# Richard R. Rosinski
# Douglas A. Host
# Test for x86/FreeBSD

#
# ${parameter:-default_value} 如果参数未赋值,将缺省值赋给它
#
dir=${1:-.}
(cd ${dir};pwd)

#
# "s,^${dir},," 删除最前面的一层目录
# "/^$/d" 删除空行
# "s,[^/]*//([^/]*/)$,/`----/1," 将最后一个/替换成`----
# "s,[^/]*/, ,g" 从左向右,将此时所有的*/替换成五个空格
#
find ${dir} -type d -print | sort -f | sed -e "s,^${dir},," /
-e "/^$/d" -e "s,[^/]*//([^/]*/)$,/`----/1," -e "s,[^/]*/, ,g"
--------------------------------------------------------------------------

下面是伪shell script,非递归删除目录树,foreach、goto都是csh才有的内部命令

--------------------------------------------------------------------------
cd target_directory
target_directory = `pwd`
while ( true )
{

loop:

rm -f *
foreach ( 本层目录所有项 )
{
if ( 子目录 )
{
rmdir 子目录
if ( 删除失败 )
{
cd 子目录
goto loop;
}
}
else
{
rm -f 普通文件
}
}
if ( `pwd` == target_directory )
{
break;
}
else
{
cd ..
}
}
cd ..
rmdir target_directory
--------------------------------------------------------------------------

下面是一个csh script,非递归删除目录树

--------------------------------------------------------------------------
#! /bin/csh -f
# Test for x86/FreeBSD
# Author : NSFocus Security Team
# : http://www.nsfocus.com
# Maintain : scz <scz@nsfocus.com>
# Usage : ./csh_rm <target_directory>
# Date : 2002-01-10 15:57

#
# 必须指定第一形参
#
if ( $#argv != 1 ) then
echo "Usage: ${0} <target_directory>"
exit 1
endif

#
# 第一形参必须是目录
#
if ( ! -d ${1} ) then
echo "Usage: ${0} <target_directory>"
exit 1
endif

#
# 这里可以先rm -rf ${1},如果失败,再用下面的方式删除
#
cd ${1}
#
# 注意csh的赋值与sh不同,需要set命令的介入
#
set target_directory = "`pwd`"
# echo ${target_directory}

alias ls '/bin/ls -1a'

#
# csh支持0、1,不支持false、true
#
while ( 1 )

loop:

#
# 对于csh,>& 表示将stdout、stderr同时转向/dev/null
#
# 其实这里可以用rm -rf * >& /dev/null,下面这句可以注释掉
#
# rm -f * >& /dev/null
#
# 不知道为什么,foreach entry ( * )存在缓冲现象,导致失败,被迫使用现
# 在演示的技术
#
foreach entry ( `ls` )
if ( "${entry}" == "." || "${entry}" == ".." ) then
continue
endif
if ( -d ${entry} ) then
#
# 这里可以用rm -rf ${entry} >& /dev/null
#
rmdir ${entry} >& /dev/null
if ( $? != 0 ) then
cd ${entry}
goto loop;
endif
else
rm -f ${entry} >& /dev/null
endif
end
set current_directory = "."
#
# 当路径超长时,pwd会报错,不必理会这个错误信息,csh无法单独处理stderr
#
set current_directory = "`pwd`"
#
# if ( "`pwd`" == "${target_directory}" ) then
#
# 如果这样写,我不确认当pwd失败时,是否会继续判断,至少目前的写法得到
# 验证,可行
#
if ( "${current_directory}" == "${target_directory}" ) then
break;
else
cd .. >& /dev/null
endif
end
cd .. >& /dev/null
rmdir ${target_directory} >& /dev/null
--------------------------------------------------------------------------

$ cp -R /tmp . <-- 为了安全起见,不要以root身份测试这个脚本
$ cp -R tmp tmp <-- 最终会出错
$ ./csh_rm tmp <-- 验证这个csh script

20.11 如何将大写文件名转换为小写文件名

A: Potash@www.linuxforum.net 2002-02-05 18:58

--------------------------------------------------------------------------
#! /bin/sh
# Usage: ./loworup.sh <-l | -u> <target_directory>

#
# 第二形参必须是目录,第一形参指定-l或-u
#
if [ $# -ne 2 ] ; then
echo "Usage: ${0} <-l | -u> <target_directory>"
exit 1
fi

if [ ! -d ${2} -o "${1}" != "-l" -a "${1}" != "-u" ] ; then
echo "Usage: ${0} <-l | -u> <target_directory>"
exit 1
fi

exec 1>/dev/null 2>&1

dir=`dirname "${2}"`
cd ${dir}

if [ "${1}" = "-l" ] ; then
base=`basename "${2}" | tr "[A-Z]" "[a-z]"`
else
base=`basename "${2}" | tr "[a-z]" "[A-Z]"`
fi

mv -f "`basename ${2}`" "${base}"

for entry in `find ${base}`
do
before="."
#
# 这个办法依赖for in语法,用空格做分隔符,所以不能处理那些本身名字带空
# 格的目录项,属于小BUG
#
for after in `echo "${entry}" | sed -e 's,/, ,g'`
do
tmp_entry="${before}/${after}"
if [ "${1}" = "-l" ] ; then
before=`echo "${tmp_entry}" | tr "[A-Z]" "[a-z]"`
else
before=`echo "${tmp_entry}" | tr "[a-z]" "[A-Z]"`
fi
mv -f "${tmp_entry}" "${before}"
done
done
--------------------------------------------------------------------------

20.12 shell script中有办法实现段落注释吗

Q: C编程中"#if 0"可以快速实现段落注释,shell script中如何达到同样效果。

A: windtear@bbs.tsinghua.edu.cn

cat << EOF > /dev/null
... ...
EOF

或者

echo << EOF > /dev/null
...
EOF

推荐使用echo。

20.13 批量文件字符串替换

A: scz <scz@nsfocus.com>

--------------------------------------------------------------------------
#! /bin/sh
# Usage: ./replace.sh <filelist> <srcstr> <dststr>

if [ $# -ne 3 ] ; then
echo "Usage: ${0} <filelist> <srcstr> <dststr>"
exit 1
fi
#
# 第一形参必须是普通文件
#
if [ ! -f ${1} ] ; then
echo "Checking your <filelist>"
exit 1
fi

while read LINE
do
if [ "${LINE}" = "" ]
then
continue
fi
# awk是为了配合grep操作,减少工作量
LINE_FILE=`echo "${LINE}" | awk -F: '{print $1;}'`
# 第一个,相当于1,$
ed - "${LINE_FILE}" << EOF
,s,${2},${3},g
w
q
EOF
done < "${1}"
--------------------------------------------------------------------------

21. BSD相关问题

21.0 在x86/FreeBSD 4.5-RELEASE上安装nessus

21.1 如何将/var文件系统mount成mfs并支持cron daemon

21.2 如何将一个512字节的文件写入主引导扇区

A: All of DOS Programmers 2001-10-16 18:05

这个问题如果在90年代初MS-DOS盛行的时候出现,是要被人砍死的,如今时过境迁,
居然能进入这份Unix文档,权当是一种追忆吧。所谓主引导扇区就是硬盘0柱面、0磁
头、1扇区。启动DEBUG,

-f 0200 l 0200 0 <-- 从0200h处开始清零,长512字节
-n mbr <-- 假设我们的要处理的文件名为mbr
-l 0200 <-- 读到0200h处
-d 03be 03ff <-- 检查分区表
XXXX:03B0 00 00 ..
XXXX:03C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA ..............U.
-

-a 100 <-- 读主引导扇区的汇编代码
XXXX:0100 mov ax, 0201 <-- 读取一个扇区,ah=02(功能码),al=01(扇区总数)
XXXX:0103 mov bx, 0400 <-- 读取后存放在0400h处,长512字节
XXXX:0106 mov cx, 0001 <-- ch=00(柱面号,10bit),cl=01(扇区号,6bit)
XXXX:0109 mov dx, 0080 <-- dh=00(磁头号),dl=80h(驱动器号)
XXXX:010C int 13 <-- int 13h 磁盘I/O BIOS
XXXX:010E int 3 <-- 单步中断,可以换成int 20h
XXXX:010F
-g=100 <-- 从0100h处开始执行

AX=0050 BX=0400 CX=0001 DX=0080 SP=FFEE BP=0000 SI=0000 DI=0000
DS=XXXX ES=XXXX SS=XXXX CS=XXXX IP=010E NV UP EI PL NZ NA PO NC
XXXX:010E CC INT 3
-d 05be 05ff <-- 检查分区表
XXXX:05B0 80 01 ..
XXXX:05C0 01 00 06 FE 3F 7F 3F 00-00 00 41 60 1F 00 00 00 ....?.?...A`....
XXXX:05D0 01 80 0F FE FF FF 80 60-1F 00 22 3C A0 01 00 00 .......`.."<....
XXXX:05E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:05F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA ..............U.
-

-m 05be l 40 03be <-- 复制分区表
-d 03be l 40 <-- 确认分区表复制成功
XXXX:03B0 80 01 ..
XXXX:03C0 01 00 06 FE 3F 7F 3F 00-00 00 41 60 1F 00 00 00 ....?.?...A`....
XXXX:03D0 01 80 0F FE FF FF 80 60-1F 00 22 3C A0 01 00 00 .......`.."<....
XXXX:03E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
XXXX:03F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 ..............
-a 100 <-- 写主引导扇区的汇编代码
XXXX:0100 mov ax, 0301 <-- 写一个扇区,ah=03(功能码),al=01(扇区总数)
XXXX:0103 mov bx, 0200 <-- 待写入数据存放在0200h处,长512字节
XXXX:0106 mov cx, 0001 <-- ch=00(柱面号,10bit),cl=01(扇区号,6bit)
XXXX:0109 mov dx, 0080 <-- dh=00(磁头号),dl=80h(驱动器号)
XXXX:010C int 13 <-- int 13h 磁盘I/O BIOS
XXXX:010E int 3 <-- 单步中断,可以换成int 20h
XXXX:010F
-g=100 <-- 从0100h处开始执行
-q <-- 退出DEBUG

第2个硬盘驱动器号是81h,修改DX寄存器赋值语句即可。

A: All of Solaris/FreeBSD/Linux Users

dd if=<path to file> of=/dev/... bs=512 count=1
^^^^^^^^ 对应要处理的物理硬盘设备

参看如下例子

# ls -li /dev/rad0 <-- 注意inode号,这里r应该是raw的含义
8051 crw-r----- 2 root operator 116, 0x00010002 /dev/rad0
# ls -li /dev/ad0
8051 crw-r----- 2 root operator 116, 0x00010002 /dev/ad0
# dd if=/dev/ad0 of=/tmp/mbr bs=512 count=1
1+0 records in
1+0 records out
512 bytes transferred in 0.000363 secs (1410498 bytes/sec)
# hexdump -C /tmp/mbr
00000000 fc 31 c0 8e c0 8e d8 8e d0 bc 00 7c be 1a 7c bf |.1.........|..|.|
00000010 1a 06 b9 e6 01 f3 a4 e9 00 8a 31 f6 bb be 07 b1 |..........1.....|
00000020 04 38 2f 74 08 7f 78 85 f6 75 74 89 de 80 c3 10 |.8/t..x..ut.....|
00000030 e2 ef 85 f6 75 02 cd 18 80 fa 80 72 0b 8a 36 75 |....u......r..6u|
00000040 04 80 c6 80 38 f2 72 02 8a 14 89 e7 8a 74 01 8b |....8.r......t..|
00000050 4c 02 bb 00 7c 80 fe ff 75 32 83 f9 ff 75 2d 51 |L...|...u2...u-Q|
00000060 53 bb aa 55 b4 41 cd 13 72 20 81 fb 55 aa 75 1a |S..U.A..r ..U.u.|
00000070 f6 c1 01 74 15 5b 66 6a 00 66 ff 74 08 06 53 6a |...t.[fj.f.t..Sj|
00000080 01 6a 10 89 e6 b8 00 42 eb 05 5b 59 b8 01 02 cd |.j.....B..[Y....|
00000090 13 89 fc 72 0f 81 bf fe 01 55 aa 75 0c ff e3 be |...r.....U.u....|
000000a0 bc 06 eb 11 be d4 06 eb 0c be f3 06 eb 07 bb 07 |................|
000000b0 00 b4 0e cd 10 ac 84 c0 75 f4 eb fe 49 6e 76 61 |........u...Inva|
000000c0 6c 69 64 20 70 61 72 74 69 74 69 6f 6e 20 74 61 |lid partition ta|
000000d0 62 6c 65 00 45 72 72 6f 72 20 6c 6f 61 64 69 6e |ble.Error loadin|
000000e0 67 20 6f 70 65 72 61 74 69 6e 67 20 73 79 73 74 |g operating syst|
000000f0 65 6d 00 4d 69 73 73 69 6e 67 20 6f 70 65 72 61 |em.Missing opera|
00000100 74 69 6e 67 20 73 79 73 74 65 6d 00 00 00 00 00 |ting system.....|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 01 |................|
000001c0 01 00 a5 0f ff ff 3f 00 00 00 b1 ff 3f 00 00 00 |......?.....?...|
000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200

D: 硬盘主引导扇区 2002-03-04 14:12

软盘没有主引导代码、分区表这些概念,我们不做讨论。下面是主引导扇区的图解

0000h +---------------------------------------------------------+
| |
| Master Boot Record |
| (主引导代码 446字节) |
01BDh | |
01BEh +---------------------------------------------------------+
| |
| 分区表项1(16字节) |
01CDh | |
01CEh +---------------------------------------------------------+
| |
| 分区表项2(16字节) |
01DDh | |
01DEh +---------------------------------------------------------+
| |
| 分区表项3(16字节) |
01EDh | |
01EEh +---------------------------------------------------------+
| |
| 分区表项4(16字节) |
01FDh | |
01FEh +---------------------------------------------------------+
| 0x55 (01FEh) | 0xAA (01FFh) |
+---------------------------------------------------------+

这就是硬盘硬盘0柱面、0磁头、1扇区(主引导扇区)的基本结构。下面介绍单个分区
表项(16字节)

每个硬盘分区表项长16字节,内容如下

第1字节 是一个分区的激活标志,0表示非活动分区,80h表示活动分区。

第2字节 该分区起始磁头号(从0开始)

8bit的磁头号可以表示256个磁头

第3字节 bit7-bit6 与第4字节一起使用

bit5-bit0 该分区起始扇区号(从1开始)

6bit的扇区号只能表示64个扇区,因为这里从1开始,所以最后只能表示63
个扇区

第4字节 bit7-bit0

第3字节的bit7-bit6作为高两位与第4字节bit7-bit0一起构成10bit的起始
柱面号,表示该分区起始柱面号(从0开始)

10bit的柱面号只能表示1024个柱面

第5字节 该分区系统类型标志,对于FreeBSD是A5h(165)

第6字节 该分区结束磁头号,与第2字节相对应

第7字节 bit7-bit6 与第8字节一起使用

bit5-bit0 该分区结束扇区号

与第3字节对应

第8字节 bit7-bit0

第7字节的bit7-bit6作为高两位与第8字节bit7-bit0一起构成10bit的结束
柱面号,表示该分区结束柱面号

与第4字节对应

第9-12字节(4)

该分区首扇区的逻辑扇区号(从0开始)

第13-16字节(4)

该分区占用的扇区总数

关察下列分区表中第二项

--------------------------------------------------------------------------
00 00 00 00 00 00 00 00 00 00-00 00 00 00 00 00
80 00 01 02 A5 FE 3F 33 82 7D-00 00 B2 41 0C 00
00 00 00 00 00 00 00 00 00 00-00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00-00 00 00 00 00 00
55 AA
--------------------------------------------------------------------------

80h活动分区,00起始磁头,01起始扇区,02起始柱面,A5h分区类型。FEh结束磁头,
3Fh结束扇区,33h结束柱面,00007D82h分区首扇区的逻辑扇区号,000C41B2h分区占
用的扇区总数。

00007D82h = 32130

000C41B2h = 803250

Q: 在MS-DOS下执行fdisk /mbr,究竟做了什么

A: 只是修改了446字节(01BEh)的主引导代码,并未修改64字节(40h)的分区表。

D: 袁哥 <yuange@nsfocus.com>

每个硬盘分区表项长16字节,内容如下

第1字节 是一个分区的激活标志,0表示非活动分区,80h表示活动分区。
第2字节 该分区起始磁头号(从零开始)
第3字节 该分区起始扇区号(从一开始)
第4字节 该分区起始柱面号(从零开始)
第5字节 该分区系统类型标志

第6-8字节(3)

该分区结束磁头号、分区结束扇区号、分区结束柱面号

第9-12字节(4)

该分区首扇区的逻辑扇区号(从零开始)

第13-16字节(4)

该分区占用的扇区总数

关察下列分区表中第二项

00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 80 00
01 02 A5 FE 3F 33 82 7D-00 00 B2 41 0C 00 00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 55 AA

80h活动分区,00起始磁头,01起始扇区,02起始柱面,A5h分区类型。FEh结束磁头,
3Fh结束扇区,33h结束柱面,00007D82h分区首扇区的逻辑扇区号,000C41B2h分区占
用的扇区总数

00007D82h = 32130

000C41B2h = 803250

D: IamRichard@bbs.gznet.edu.cn

对第3、4字节的解释,bit0-bit5为起始扇区号,bit6-bit7为起始柱面号的高两位,
即第3字节的bit6-bit7作为高两位与第4字节一起构成10bit的起始柱面号。第7、8字
节亦有相似解释。10bit的柱面号只能表示1024个柱面。

D: WYHui@bbs.gznet.edu.cn

0磁道、0柱面、2扇区~0磁道、0柱面、63扇区在缺省情况下均是保留扇区,但可以
使用,只要保证没有别人使用就行。下面是MBR源代码

--------------------------------------------------------------------------
PartLoad equ 600h
BootLoc equ 7c00h
.MODEL tiny
.CODE
org 0
Head:
Start:
cli
xor ax,ax
mov ss,ax
mov sp,7c00h
mov si,sp
push ax
pop es
push ax
pop ds
sti
cld
mov di,PartLoad
mov cx,100h
repne movsw
db 0eah
dw offset Continue+600h,0000h
Continue:
mov si,PartLoad+1beh
mov bl,4
FindBoot:
cmp byte ptr[si],80h
je SaveRec
cmp byte ptr[si],0
jne Invalid
add si,10h
dec bl
jnz FindBoot
int 18h
SaveRec:
mov dx,[si]
mov cx,[si+2]
mov bp,si
FindNext:
add si,10h
dec bl
jz SetRead
cmp byte ptr[si],0
je FindNext
Invalid:
mov si,offset ErrMsg1+600h
PrintStr:
lodsb
cmp al,0
je DeadLock
push si
mov bx,7
mov ah,0eh
int 10h
pop si
jmp short PrintStr
DeadLock:
jmp short DeadLock
SetRead:
mov di,5
ReadBoot:
mov bx,BootLoc
mov ax,201h
push di
int 13h
pop di
jnc GoBoot
xor ax,ax
int 13h
dec di
jnz ReadBoot
mov si,offset ErrMsg2+600h
jmp short PrintStr
GoBoot:
mov si,offset ErrMsg3+600h
mov di,7c00h+1feh
cmp word ptr[di],0aa55h
jne PrintStr
mov si,bp
db 0eah,00h,7ch,00h,00h
ErrMsg1 db 'Invalid Partition table',0
ErrMsg2 db 'Error loading operating system',0
ErrMsg3 db 'Missing operating system',0
Tail:
FillNum equ 1beh-(Tail-Head)
db Fillnum dup(0)
PartTable:
table1 db ' '
table2 db ' '
table3 db ' '
table4 db ' '
MagicID dw 0aa55h
End Start
--------------------------------------------------------------------------

21.3 x86/FreeBSD 4.3-RELEASE下FDISK(8)手册页

21.4 x86/FreeBSD 4.3-RELEASE下HEXDUMP(1)手册页

21.5 x86/FreeBSD 4.3-RELEASE下DISKLABEL(8)手册页

21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel

Q: 重新编译内核后用cp命令无法覆盖/kernel

A: deepin <deepin@nsfocus.com>

# ls -lo /kernel
-rwxr-xr-x 1 root wheel schg /kernel*
^^^^注意这里,类似Linux的chattr那些东西
# chflags noschg /kernel

参看CHFLAGS(1)、INSTALL(1)手册页。这样修改后可以cp覆盖/kernel了。最后恢复
chflags设置

# chflags schg /kernel

21.7 x86/FreeBSD下如何设置路由

A: backend <backend@nsfocus.com> 2001-10-25 11:33

/etc/defaults/rc.conf或者/etc/rc.conf中会有这样的设置

--------------------------------------------------------------------------
defaultrouter="NO" # Set to default gateway (or NO).
static_routes="" # Set to static route list (or leave empty).
--------------------------------------------------------------------------

下面分析static_routes的用法,从/etc/rc.network脚本中可以看到这样的处理

--------------------------------------------------------------------------
# Configure routing
#
case ${defaultrouter} in
[Nn][Oo] | '')
;;
*)
static_routes="default ${static_routes}"
route_default="default ${defaultrouter}"
;;
esac

# Set up any static routes. This should be done before router discovery.
#
if [ -n "${static_routes}" ]; then
for i in ${static_routes}; do
eval route_args=/$route_${i}
route add ${route_args}
done
fi
--------------------------------------------------------------------------

注意eval命令导致二次变量替换,对上述脚本分析后可知static_routes用法如下

--------------------------------------------------------------------------
defaultrouter="<IP>"
static_routes="<name1> <name2> ..."
route_<name1>="符合route add命令的语法格式"
route_<name2>="符合route add命令的语法格式"
... ...
--------------------------------------------------------------------------

举例说明

--------------------------------------------------------------------------
defaultrouter="192.168.0.1"
static_routes="entry1 entry2"
route_entry1="-net 10.10.1.0 -netmask 255.255.255.0 -gateway 192.168.254.1"
route_entry2="-net 10.10.2.0 -netmask 255.255.255.0 -gateway 192.168.254.2"
--------------------------------------------------------------------------

当然,你可以不用两个rc.conf文件,而是在/etc/rc.local中直接用route命令增加
路由。

21.8 x86/FreeBSD 4.4-RELEASE下DIFF(1)手册页

21.9 什么是locale

A: Shen Chuan-Hsing <statue@freebsd.sinica.edu.tw>

本文来自<<FreeBSD Chinese HOWTO>>,参看如下链接
http://freebsd.sinica.edu.tw/~statue/zh-tut/

locale 指定一组C语言处理自然语言(文字)的方式,也可以简单地说,locale反映了
一组"地区性语言"的配置信息

LC_ALL 代表所有的locale(如下)

LC_CTYPE 字符定义(包含字符分类与转换规则)

LC_MESSAGES 信息显示

LC_TIME 时间格式

LC_NUMERIC 数字格式

LC_MONETARY 货币格式

LC_COLLATE 字母顺序与特殊字符比较顺序

其中与一般使用者息息相关的是是LC_CTYPE与LC_MESSAGES。LC_CTYPE直接关系到某
些字符或內码在目前locale下是否可显示?要如何转换编码?对应到哪一个字?等等。
LC_MESSAGES则关系到软件的信息输出是否符合地域性,例如:我们需要的是中文。
而一个真正完整支持locale系统,是当使用者在shell prompt下,直接设置好环境变
量后就马上切换到那种语言了,例如:

% export LC_CTYPE=zh_TW.Big5

设置locale的字符定义为台湾地区的Big5繁体中文码定义。有了正确的locale定义后,
使得任何地区的的文字,只要在加入适当的locale data之后,C Library就能正确地
处理软件显示信息,而我们使用的[中文]当然也不例外。

21.10 用cvsup安装vim

21.11 FreeBSD下显示、输入中文

21.12 如何在OpenSSH中限制只允许某些用户登录

A: tt <warning3@nsfocus.com> 2001-11-20 10:56

编辑/etc/ssh/sshd_config,在最后增加下列语句

AllowUsers support

这将只允许"support"帐号通过ssh登录系统,如果需要增加其他用户,可以使用空格
隔开。支持*、?通配符。

对于root帐号,单独有一个配置选项

PermitRootLogin yes

21.13 在FreeBSD 4.3-RELEASE上安装libpcap、libnet

21.14 如何使自己的BMP图象成为启动logo

21.15 UDMA ICRC error是什么意思

Q: 在console上出现错误信息"UDMA ICRC error writing... ...",什么意思

A: tt <warning3@nsfocus.com>

通常是使用了40线的IDE硬盘线,然而硬盘被设置成使用DMA模式,这种模式需要80线
硬盘线。也有可能是您的硬盘不支持DMA方式。解决方法有几种

1) 换用一根80线的IDE硬盘线(没干过)
2) 在CMOS BIOS中关闭对UDMA的支持
3) 在FreeBSD中关闭对UDMA的支持

vi /etc/sysctl.conf
hw.atamodes=pio,pio,pio,pio,

这样做,可能会降低硬盘速率。

21.16 Limiting closed port RST response什么意思

Q: console上出现"Limiting closed port RST response",什么意思

A: tt <warning3@nsfocus.com>

某些主机快速访问你的主机上一些没有开放的端口,你的主机正在回复RST报文,这
是正常反应。但FreeBSD内核限制了每秒钟回复RST报文的数量,以防止发生可能的
DoS攻击。例如,如果攻击者通过伪造源IP来向你的未开端口发送大量连接请求,就
可能诱使你的主机向该主机发送RST报文。这可能导致受害主机所在网络的带宽占用。
如果你不想看到上述信息,可以打开黑洞模式来停止响应RST报文。这也可以减缓远
程攻击者对你的主机的扫描速度。

# sysctl -w net.inet.tcp.blackhole=2
# sysctl -w net.inet.udp.blackhole=1

也可以在/etc/sysctl.conf中增加下列选项使黑洞模式每次启动后都生效

net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1

21.17 如何获取FreeBSD Kernel Source Code

Q: 没有装FreeBSD Kernel Source Code,但现在想在Windows下用Source Insight分
析内核源码,怎么办

A: 放入FreeBSD安装光盘,手动mount光驱,做如下操作

# dmesg | grep -i CDROM
acd0: CDROM <ATAPI-CD ROM-DRIVE-50MAX> at ata1-slave using PIO4
# ls -l /dev/acd0*
crw-r----- 2 root operator 117, 0 Nov 15 21:46 /dev/acd0a
crw-r----- 2 root operator 117, 2 Nov 15 21:46 /dev/acd0c
# mount -t cd9660 -r /dev/acd0a /cdrom
# cd /tmp
# cat /cdrom/src/ssys.[ab]? > FreeBSD_44R_ssys.tgz
# cat /cdrom/src/slib.[ab]? > FreeBSD_44R_slib.tgz
# cat /cdrom/src/slibexec.[ab]? > FreeBSD_44R_slibexec.tgz
# cat /cdrom/src/sgnu.[ab]? > FreeBSD_44R_sgnu.tgz
# cat /cdrom/src/srelease.[ab]? > FreeBSD_44R_srelease.tgz
# cat /cdrom/src/sbin.[ab]? > FreeBSD_44R_sbin.tgz
# cat /cdrom/src/ssbin.[ab]? > FreeBSD_44R_ssbin.tgz
# cat /cdrom/src/subin.[ab]? > FreeBSD_44R_subin.tgz
# cat /cdrom/src/susbin.[ab]? > FreeBSD_44R_susbin.tgz
# cat /cdrom/src/sbase.[ab]? > FreeBSD_44R_sbase.tgz
# cat /cdrom/src/sshare.[ab]? > FreeBSD_44R_sshare.tgz
# cat /cdrom/src/stools.[ab]? > FreeBSD_44R_stools.tgz
# cat /cdrom/src/sinclude.[ab]? > FreeBSD_44R_sinclude.tgz

这些FreeBSD*.tgz可以下载回Windows系统中,winzip打开即可。

21.18 /boot/defaults/loader.conf中的技巧

D: 小四 <scz@nsfocus.com> 2002-01-09 13:46

1) 启动时加载/kernel前的10s暂停如何调整

autoboot_delay="10" # Delay in seconds before autobooting

21.19 FreeBSD中sysctl可控内核参数

D: 小四 <scz@nsfocus.com> 2002-01-10 10:31

1) kern.ps_showallprocs

参看sys/kern/kern_proc.c,该值缺省是1,如果
sysctl -w kern.ps_showallprocs=0,则只有root用户才能看到所有进程(ps -aux),
其它用户不能看到非自己所有的进程。

2) kern.maxfiles
kern.maxfilesperproc

前者是整个系统所能打开的最大文件句柄数,后者是每个进程所能打开的最大文件句
柄数

3) kern.maxproc
kern.maxprocperuid

前者是系统所允许的最大进程数,后者是每个UID所能拥有的最大进程数

4) net.inet.icmp.bmcastecho

0 忽略广播、组播包
1 响应广播、组播包

5) net.inet.ip.forwarding

0 关闭IP转发
1 打开IP转发

6) net.inet.tcp.log_in_vain
net.inet.udp.log_in_vain

如果设置为1就会在日志中记录针对本机的端口扫描。

7) kern.ps_argsopen (FreeBSD 4.7-STABLE)

0 在ps的输出中不显示argv[]数组
1 在ps的输出中显示argv[]数组

21.20 x86/FreeBSD 4.3-RELEASE下GETIFADDRS(3)手册页

21.21 FreeBSD下如何访问显存

Q:

我在开发一个FreeBSD上的内核模块,需要读写VGA文本模式屏幕缓冲区,也就是物理
地址0xB8000。如果在MS-DOS下,当然可以直接访问这个地址,但现在我在FreeBSD内
核中,必须使用内核虚拟地址,我的问题是如何"正确地"转换物理地址到内核虚拟地
址。目前我用了如下技巧,0xC0000000 + 0xB8000,但这是不规范的。

A: "M. Warner Losh" <imp@village.org>

1) bus_alloc_resource()
2) bus_space_{read,write}_*

D: 小四 <scz@nsfocus.com>

应该也可以用mmap()通过/dev/mem映射0xB8000吧,参看"直接访问内存[显存]地址"

21.22 FreeBSD下如何为指定用户设定chroot的FTP环境

A: backend <backend@nsfocus.com> 2002-03-19 11:13

将该用户帐号添加到/etc/ftpchroot文件中,则该帐号FTP登录后被限制在其登录目
录下。如果希望限制用户组,则在组名前添加@字符并写入/etc/ftpchroot文件中。
修改后立即生效,不需要重启inetd进程。

# cat /etc/ftpchroot
badguy
@badgroup
#

21.23 如何利用FKLD动态增加一个新协议

Q: Marco Molteni <molter@tin.it>

在netinet/in_proto.c中有一个结构struct ipprotosw inetsw[],包含了各种IP协
议的入口。如果想增加一个新协议,就必须在这个结构数组中增加入口。我想知道能
否利用FKLD方式增加一个新协议

A: Brooks Davis <brooks@one-eyed-alien.net>

看看sys/net/if_gif.c,这里增加了一个新的IP协议,而且是可加载的。

21.24 修改/etc/mail/sendmail.cf关闭ident功能

Q: FreeBSD系统,本地/远程使用sendmail发信时缓慢,但TCP连接建立正常,为何

A: backend <backend@nsfocus.com>

用telnet连接25端口,发现在TCP连接建立后,第一条提示信息需要等待几秒才能出
现。利用netstat命令查看后发现sendmail进程在此之前连接客户端的113端口(即
ident),基本确定是这个查询客户端用户的操作产生延迟。修改
/etc/mail/sendmail.cf配置文件,关闭ident(RFC 1413)功能。

#O Timeout.ident=5s
O Timeout.ident=0s

21.25 FreeBSD下如何获取系统负载

A: Anthony Schneider <aschneid@mail.slc.edu>

可以用getloadavg(3)或者kvm_getloadavg(3)。进程信息可用kvm_getprocs(3)获取。

21.26 *BSD下如何屏敝远程登录时Copyright显示

A: 在自己主目录下生成一个文件.hushlogin

echo "ALLOWHUSH=NO" > .hushlogin

此外还有/etc/motd、/etc/issue

21.27 cvsup安装BASH

21.28 配置core dump

Q: 我在日志中看到

pid 36861 (ftpd), uid 29987: exited on signal 11

这是x86/FreeBSD 4.5-RELEASE-p3自带的ftpd。我使用-g参数重新编译了ftpd以
便调试

mkdir /var/coredump
chmod 1777 /var/coredump
sysctl kern.corefile=/var/coredump/%U.%N.%P.core (缺省为%N.core)

在/etc/login.conf中指定了

coredumpsize=unlimited

但是当ftpd崩溃时,依旧没有core dump,我不想等到4.6-RELEASE才解决这个问
题。

A: Mikko Tyolajarvi <mikko@dynas.se>

试试 sysctl kern.sugid_coredump=1 (缺省为0)

如果ftpd崩溃发生在用户登录完成之后,UID != EUID,必须做如上指定。遗憾的是,
这个设置并未解决前面的问题,不知还有什么地方限制了core dump产生?

D: Garrett Wollman <wollman@lcs.mit.edu>

设计FTP协议之初没有考虑到"特权端口"的问题,而在(服务器)主动模式下数据流绑
定源端口20/TCP,这需要root权限。如果ftpd不是以root身份启动,就无法完成这种
绑定。但又非始终需要root权限,所以有可能调用seteuid()、setreuid()临时放弃
特权。

某些ftpd的实现考虑到特权端口的问题,允许指定(服务器)主动模式下数据流是否绑
定20/TCP(违背RFC 959的要求),此时ftpd不必保持root权限。

至于控制流的21/TCP,多是来自inetd、xinetd,一旦TCP连接建立,就不需要root权
限了。

21.29 在OpenBSD 3.0上安装Gcc

21.30 在NetBSD 1.5.2上安装BASH

21.31 找不到何处启动了snmpd

21.32 FreeBSD远程root访问

A: 编辑/etc/ttys

# Pseudo terminals
ttyp0 none network off secure
ttyp1 none network off secure
ttyp2 none network off secure
ttyp3 none network off secure

22. Linux Kernel Programming

22.1 直接访问内存[显存]地址

Q: 现在在修改linux内核,希望能访问一段地址(其实是显存)。但发觉不能直接访问

A: Kongming <ymwei@263.net> (Luther <Luther@pku.edu> 整理)

通过/dev/mem设备文件和mmap系统调用,可以将线性地址描述的物理内存映射到进程
的地址空间,然后就可以直接访问这段内存了。

比如,标准VGA 16色模式的实模式地址是A000:0000,而线性地址则是A0000。设定显
存大小为0x10000,则可以如下操作

mem_fd = open( "/dev/mem", O_RDWR );
vga_mem = mmap( 0, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED,
mem_fd, 0xA0000 );
close( mem_fd );

然后直接对vga_mem进行访问,就可以了。当然,如果是操作VGA显卡,还要获得I/O
端口的访问权限,以便进行直接的I/O操作,用来设置模式/调色板/选择位面等等

在工控领域中还有一种常用的方法,用来在内核和应用程序之间高效传递数据:

1) 假定系统有64M物理内存,则可以通过lilo通知内核只使用63M,而保留1M物理内
存作为数据交换使用(使用 mem=63M 标记)。
2) 然后打开/dev/mem设备,并将63M开始的1M地址空间映射到进程的地址空间。

22.2 /proc可控内核参数

1) /proc/sys/net/ipv4/ip_forward

0 关闭IP转发
1 启动IP转发

# echo 1 > /proc/sys/net/ipv4/ip_forward

2) /proc/sys/net/ipv4/icmp_echo_ignore_all
/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts

0 响应
1 忽略

23. Linux相关问题

23.0 以POST方式提交URL请求

A: san <san@nsfocus.com>

$ echo "uid=username&password=secret" | lynx -post_data http://www.url.com

A: tombkeeper <tombkeeper@nsfocus.com>

curl -v http://cygnus.tele.pw.edu.pl/cgi-bin/environ.pl-d xxx

23.1 RedHat 7.2远程root访问

Q: 缺省安装RedHat Linux 7.2后我想远程telnet、ftp访问之,以root身份

A: 2002-03-03 17:37

尽管从安全角度不建议如此做,但的确是可以实现的

1) vi /etc/securetty,增加类似行

pts/0
pts/1
pts/2
... ...

此后root可以远程telnet登录

2) vi /etc/ftpusers,注释或删除root帐号

3) vi /etc/ftpaccess,修改得到

allow-uid root
allow-gid root

此后root可以远程ftp登录

D: hhuu@smth 2003-06-10

如果从127.0.0.1以外的地址telnet访问,TCP连接被立刻切断的话,需要修改
/etc/hosts.allow文件。"cat /var/log/security"可以看到这样做的原因。

RedHat Linux 7.2缺省安装结束后,/etc/hosts.allow文件没有额外设置过。

23.2 TELNET/FTP连接耗时过长

Q: Redhat Linux 7.2 wu-ftpd,在LAN内时FTP连接正常,托管到电信(不在同一LAN)
后,FTP连接耗时过长,大约30s。

A: 木犀

有两种可能

1) 反向域名解析耗时,此时可在/etc/hosts增加相应client ip条目

2) RFC 931认证查询(113/TCP)

wu-ftpd支持RFC 931认证查询,大约10s才放弃。如果client位于NAT、Firewall
之后,server无法主动向client的113/TCP建立TCP连接,就必然等待10s超时。解
决办法有两种

a. 允许server主动向client的113/TCP建立TCP连接,此时不要求client上identd
真实存在,server发现client回送RST包便不再等待。

b. vi /etc/xinetd.d/wu-ftp

删除如下两行并重启xinetd

log_on_success += DURATION USERID
log_on_failure += USERID

D: flowbusily@bbs.apue.net 2002-10-10

我使用proftpd来做FTP Server,文中提到的反向域名解析与113/TCP认证在proftpd
里用如下两个选项来解决:

UseReverseDNS off
IdentLookups off

D: hek@smth 2003-04-08

配置服务器不要试图连接客户端的113/TCP

/etc/xinetd.d/telnet

log_on_failure += USERID

修改如下

log_on_failure += HOST

/etc/xinetd.d/wu-ftpd

log_on_success += DURATION USERID
log_on_failure += USERID

修改如下

log_on_success += DURATION HOST
log_on_failure += HOST

23.3 Debian/Linux中如何修改本机IP

A: 修改/etc/network/interfaces、/etc/hosts文件

--------------------------------------------------------------------------
# /etc/network/interfaces -- configuration file for ifup(8), ifdown(8)

# The loopback interface
auto lo
iface lo inet loopback

# The first network card - this entry was created during the Debian
# installation (network, broadcast and gateway are optional)
auto eth0
iface eth0 inet static
address 192.168.7.148
netmask 255.255.255.0
network 192.168.7.0
broadcast 192.168.7.255
gateway 192.168.7.254
--------------------------------------------------------------------------

23.4 如何确认是何种Unix Release

Q: "uname -a"看不出来是RedHat,如何确认是RedHat

A: $ cat /etc/redhat-release
Red Hat Linux release 7.2 (Enigma)

D: melonm@smth 2003-06-08

类似的操作有:

cat /etc/release (for solaris)
cat /etc/issue (for some others)
cat /etc/slackware-version (for slackware)

如果"uname -a"可以看出是何种Unix,就不必这样操作,所以Solaris用不着这样。

23.5 vi/insert状态下copy/paste时不回车、只换行

Q: 远程telnet/ssh登录,在vi/insert状态下copy/paste时不回车、只换行,结果显
示逞向屏幕右下角的阶梯状。多次在Linux中遇上该问题,如何解决。

A: 编辑~/.vimrc文件,放入如下两行内容:

set noautoindent
set nocindent

如果只是临时改动,可在vi的命令状态输入上述两条命令。还有一种可能,就是
汉学处理带来的问题,此时可修改环境变量:

ls -la /usr/lib/locale
export LANG=zh_CN.gbk

23.6 如何产生core dump

Q: 环境: Linux/pthread/g++

我的程序存在一个很难重现的BUG,它导致Segment fault,但未core dump。当我
用GDB调试该程序时,这个BUG"消失"了。我该如何定位BUG。

D: csmith@micromuse.com

试试pthread_kill_other_threads_np(),使之成为信号句柄:

--------------------------------------------------------------------------
struct sigaction sa = { 0 };

/*
* Set the handler for SEGV, BUS, ILL and ABRT to the Linux thread
* function below, to kill the threads so we can produce a core dump.
* It should be safe using pthread_kill_other_threads_np directly,
* as it takes no arguments (will ignore the signo) and returns void
* also.
*/
sa.sa_handler = pthread_kill_other_threads_np;
sa.sa_flags = SA_RESETHAND | SA_RESTART;

sigaction( SIGSEGV, &sa, NULL );
sigaction( SIGBUS, &sa, NULL );
sigaction( SIGILL, &sa, NULL );
sigaction( SIGABRT, &sa, NULL );
--------------------------------------------------------------------------

我们用这种办法在Linux上调试多线程程序。可能会丢失其它线程的信息,但聊胜于
无。

23.7 Socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错

Q: Socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错

socket() failed: Address family not supported by protocol

A: 重新配置、编译内核

CONFIG_PACKET=y

这是缺省设置。如果未使能该选项,就会得到上述错误信息。

24. Unix编程相关问题

24.0 如何知道fd是有效文件句柄

Q: 如果一个函数需要一个有效文件句柄(可以是pipe、fd、socket等等)做为形参,
有无办法判断其有效性。

A: Joe Halpin <jhalpin@nortelnetworks.com_.nospam>

我觉得最好是用fstat()函数进行判断,当fd不是一个有效文件句柄时,该函数返
回-1,并设置errno为EBADF。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

fcntl( fd, F_GETFD, 0 );

可能这是最快的办法,也最具有可移植性。在那些支持procfs的系统中,也可以
检查/proc/<pid>/fd/目录下的伪文件,比如SPARC/Solaris 8和x86/Linux
Kernel 2.4.7-10。而x86/FreeBSD 4.5-RELEASE中没有/proc/<pid>/fd/目录。显
然这种技术可移植性差了很多。

24.1 如何使代码段可写

Q: 如下演示程序试图对代码段进行写操作,缺省情况下必然失败,有何建议。

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/Linux RedHat/7.2 2.4.7-10(gcc 2.96/gas 2.11.90.0.8)
* : gcc -static -Wall -pipe -g -o src src.c
* -----------------------------------------------------------------------
*/
#include <stdio.h>

int main ( int argc, char * argv[] )
{
unsigned int *p;

p = ( unsigned int * )&main;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
*p = 0x4F46534E;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
return( 0 );
} /* end of main */
--------------------------------------------------------------------------

$ ./src
[0x080481E0] -> 0x83E58955
Segmentation fault (core dumped)
$

A: scz <scz@nsfocus.com>

无论是哪种Unix系统,总可以利用mprotect()设置PC附近的内存权限为rwx:

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/Linux RedHat/7.2 2.4.7-10(gcc 2.96/gas 2.11.90.0.8)
* : gcc -static -Wall -pipe -g -o src_other src_other.c
* -----------------------------------------------------------------------
*/
#include <stdio.h>
#include <sys/mman.h>

int main ( int argc, char * argv[] )
{
unsigned int *p = ( unsigned int * )( ( unsigned int )&main & 0xffffc000 );

if ( mprotect( p, 0x4000, 7 ) < 0 )
{
perror( "mprotect error" );
return( -1 );
}
p = ( unsigned int * )&main;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
*p = 0x4F46534E;
printf( "[0x%08X] -> 0x%08X/n", ( unsigned int )p, *p );
return( 0 );
} /* end of main */
--------------------------------------------------------------------------

$ ./src_other
[0x080481E0] -> 0x83E58955
[0x080481E0] -> 0x4F46534E

修改静态文件中代码段的p_flags,从(PF_R | PF_X)改为(PF_R | PF_W | PF_X),这
样的ELF文件加载后,代码段缺省可写。下面是一个简单的跨平台可移植演示程序:

--------------------------------------------------------------------------
/*
* -----------------------------------------------------------------------
* Compile : For x86/Linux RedHat_8 2.4.18-14
* : For x86/FreeBSD 4.5-RELEASE
* : For SPARC/Solaris 8
* : gcc -Wall -pipe -O3 -o codew codew.c
* -----------------------------------------------------------------------
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PT_LOAD 1 /* Loadable program segment */
#define PF_X (1 << 0) /* Segment is executable */
#define PF_W (1 << 1) /* Segment is writable */
#define PF_R (1 << 2) /* Segment is readable */

/*
* The ELF file header. This appears at the start of every ELF file.
*/
struct ELF32EH
{
unsigned char e_ident[16]; /* Magic number and other info */
unsigned short int e_type; /* Object file type */
unsigned short int e_machine; /* Architecture */
unsigned int e_version; /* Object file version */
unsigned int e_entry; /* Entry point virtual address */
unsigned int e_phoff; /* Program header table file offset */
unsigned int e_shoff; /* Section header table file offset */
unsigned int e_flags; /* Processor-specific flags */
unsigned short int e_ehsize; /* ELF header size in bytes */
unsigned short int e_phentsize; /* Program header table entry size */
unsigned short int e_phnum; /* Program header table entry count */
unsigned short int e_shentsize; /* Section header table entry size */
unsigned short int e_shnum; /* Section header table entry count */
unsigned short int e_shstrndx; /* Section header string table index */
} __attribute__ ((packed));

struct ELF32PH
{
unsigned int p_type; /* Segment type */
unsigned int p_offset; /* Segment file offset */
unsigned int p_vaddr; /* Segment virtual address */
unsigned int p_paddr; /* Segment physical address */
unsigned int p_filesz; /* Segment size in file */
unsigned int p_memsz; /* Segment size in memory */
unsigned int p_flags; /* Segment flags */
unsigned int p_align; /* Segment alignment */
} __attribute__ ((packed));

int main ( int argc, char * argv[] )
{
struct ELF32EH eh;
struct ELF32PH ph;
unsigned short int e_phnum;
int fd = -1;

if ( argc != 2 )
{
fprintf( stderr, "Usage: %s <32-bit ELF file>/n", argv[0] );
return( EXIT_FAILURE );
}
if ( ( fd = open( argv[1], O_RDWR ) ) < 0 )
{
perror( "open error" );
return( EXIT_FAILURE );
}
if ( read( fd, &eh, sizeof( eh ) ) != sizeof( eh ) )
{
printf( "read eh error/n" );
goto main_0;
}
for ( e_phnum = 0; e_phnum < eh.e_phnum; e_phnum++ )
{
if ( lseek( fd, eh.e_phoff + e_phnum * sizeof( ph ), SEEK_SET ) < 0 )
{
perror( "lseek error" );
goto main_0;
}
if ( read( fd, &ph, sizeof( ph ) ) != sizeof( ph ) )
{
printf( "read ph error/n" );
goto main_0;
}
if ( ( ph.p_type == PT_LOAD ) && ( ( ph.p_flags & ( PF_R | PF_X ) ) ==
( PF_R | PF_X ) ) )
{
printf( "old ph.p_flags = 0x%08x/n", ph.p_flags );
ph.p_flags |= PF_W;
printf( "new ph.p_flags = 0x%08x/n", ph.p_flags );
lseek( fd, eh.e_phoff + e_phnum * sizeof( ph ), SEEK_SET );
if ( write( fd, &ph, sizeof( ph ) ) != sizeof( ph ) )
{
printf( "write ph error/n" );
goto main_0;
}
break;
}
} /* end of for */

main_0:

close( fd );
fd = -1;
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

下面在SPARC/Solaris 8上测试效果:

$ ./src
[0x0001080C] -> 0x9DE3BF88
段错误 (core dumped)
$ elfdump -p src
程序头[2]:
p_vaddr: 0x10000 p_flags: [ PF_X PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x934 p_memsz: 0x934
p_offset: 0 p_align: 0x10000
$ ./codew src
old ph.p_flags = 0x00000005
new ph.p_flags = 0x00000007
$ elfdump -p src
程序头[2]:
p_vaddr: 0x10000 p_flags: [ PF_X PF_W PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x934 p_memsz: 0x934
p_offset: 0 p_align: 0x10000
$ ./src
[0x0001080C] -> 0x9DE3BF88
[0x0001080C] -> 0x4F46534E

对于Linux、FreeBSD有readelf工具可用:

$ readelf -l src
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz *** Align
LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 R E 0x1000
$ ./codew src
old ph.p_flags = 0x00000005
new ph.p_flags = 0x00000007
$ readelf -l src
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz *** Align
LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 RWE 0x1000
$

用"objdump -x src | more"也能看到这些信息。

"law@bbs.apue.net"曾经建议过这样的命令:

$ ./src
[0x08048494] -> 0x83E58955
Bus error (core dumped)
$ objdump -h src
Sections:
Idx Name Size VMA LMA File off Algn
8 .text 00000190 08048388 08048388 00000388 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
$ objcopy --set-section-flags .text=CONTENTS,ALLOC,LOAD,CODE src dst
$ objdump -h dst
Idx Name Size VMA LMA File off Algn
8 .text 00000190 08048388 08048388 00000388 2**2
CONTENTS, ALLOC, LOAD, CODE
$ ./dst
[0x08048494] -> 0x83E58955
Bus error (core dumped)
$

这条objcopy命令修改了.text的sh_flags,而不是全局的p_flags,去掉READONLY也
未能使得代码段缺省可写。

"watercloud@nsfocus.com"建议过另一种邪门办法,下面在SPARC/Solaris 8上演示:

$ gcc -Wall -S -o src.s src.c

编辑src.s文件,将main()所在的节名由.text改成.data,继续编译:

$ gcc -static -Wall -pipe -g -o src src.s
$ ./src
[0x0004E770] -> 0x9DE3BF88
[0x0004E770] -> 0x4F46534E
$ elfdump -p src
程序头[0]:
p_vaddr: 0x10078 p_flags: [ PF_X PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x2e67e p_memsz: 0x2e67e
p_offset: 0x78 p_align: 0x10000
程序头[1]:
p_vaddr: 0x4e6f8 p_flags: [ PF_X PF_W PF_R ]
p_paddr: 0 p_type: [ PT_LOAD ]
p_filesz: 0x1a31 p_memsz: 0x2640
p_offset: 0x2e6f8 p_align: 0x10000
$

24.2 建议性文件锁与强制性文件锁

D: 某些Unix系统(SVR4)支持强制性文件锁。假设一个文件被设置过强制性文件锁,
此时操作系统内核将阻止其它进程对该文件进行creat、open、read、write操作,
errno设置成EAGAIN。因此强制性文件锁比较危险,如果对某个比较重要的系统文
件设置过强制性文件锁又忘记释放,有可能导致系统崩溃。设置强制性文件锁的
方式比较特别:

chmod g+s <filename>
chmod g-x <filename>

这是形象的表示,实际编程中应该通过chmod()函数一步完成。不能对目录、可执
行文件设置强制性锁。

24.3 如何编写daemon程序

Q: 在FreeBSD下用"ps auxw"查看进程列表时,注意到某些进程没有控制终端,也就
是说TT列显示??。知道这是所谓的daemon进程,如果我想自己编写这样的程序,
该如何做。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>

这个回答来自著名的<<Unix Programming FAQ ver 1.37>>,由Andrew Gierth负责维
护,其它细节请参看原文1.7小节。

通常将一个不与任何终端相关联的后台进程定义为daemon进程。下面是通常所需的七
个步骤:

a. fork()之后父进程退出。子进程确保不是process group leader,这是成功调用
setsid()所要求的。

b. setsid(),创建新的session和process group,成为其leader,并脱离控制终端。

c. 再次fork()之后父进程退出,子进程确保不是session leader,将永远不会重获
控制终端。这是SVR4的特性所致。

d. chdir( "/" ),减少管理员卸载(unmount)文件系统时可能遇上的麻烦。这一步可
选,也可chdir()到其它目录。

e. umask( 0 ),使当前进程对自己所写文件拥有完全控制权,避免继承的umask()设
置带来困挠。这一步可选。

f. 关闭0、1、2三个句柄。许多daemon程序用sysconf()获取_SC_OPEN_MAX,并在一
个偱环中关闭所有可能打开的文件句柄。目的在于释放不必要的系统资源,它们
是有限资源。

g. 出于安全以及健壮性考虑,即使当前进程不使用stdin、stdout、stderr,也应重
新打开0、1、2三个句柄,使之对应/dev/null。当然,你也可以根据需要使之对
应不同的(伪)文件。总之,保持0、1、2三个句柄呈打开状态,并使之指向无害文
件。

D: scz <scz@nsfocus.com>

以FreeBSD 4.5-RELEASE为例进行讨论。

注意,存在与终端相关联的后台进程,比如在支持作业控制的bash上以&符结尾启动
的进程。当用"ps auxw"查看时,这种后台进程的TT列不为??。用"ps -p pid -jfl"
查看这种后台进程,可以看到其PGID与父进程的PGID不同,属于另外一个进程组。支
持作业控制的现代shell对&符的解释一般都是fork/setpgid。前台进程组、后台进程
组是终端的属性,不是进程本身的属性,没有控制终端的进程无所谓前台、后台,一
定要算就都算是后台进程。

非作业控制型的shell对&符的解释一般只是fork,而没有setpgid,这样启动的进程
与shell属于同一进程组。后面的讨论都假设使用支持作业控制的现代shell。

当在控制终端上按下Ctrl-C,终端驱动程序产生SIGINT信号(可用stty设置)并分发至
前台进程组的所有进程。

APUE 10.2中提到,当session leader终止时,系统会向该session前台进程组中所有
进程分发SIGHUP信号。我的疑问是,如果某session没有控制终端,也就没有所谓前
台进程组,当session leader终止时,系统会向该session中所有进程分发SIGHUP信
号吗。UNP 12.4的例子正是这种情形,可是Stevens没有在其它地方进一步阐述,也
永远不可能得到他本人的解释了。

APUE 13.3所给的daemon_init()与UNP 12.4所给不同,没有做二次fork()。因为二次
fork()只是SVR4的要求。从最广泛兼容角度出发,如果daemon进程企图打开一个(伪)
终端设备,无论是否二次fork()过,open()时都应该指定O_NOCTTY。由于daemon程序
是自己完全可控的,将来是否会打开终端是已知的,如果确认将来不会打开终端,就
完全不必考虑重获控制终端的问题,换句话说,二次fork()很大程度上是不必要的。

关于Andrew Gierth所提第七步骤,1987年Henry Spencer在setuid(7)手册页中做了
相关建议,1991年,在comp news上有人重贴了这份文档。1992年Richard Stevens建
议daemon进程应该关闭所有不必要的文件句柄,并将stdin、stdout、stderr指向
/dev/null。参看<<x86/FreeBSD 4.5-RELEASE IO Smash及S/Key机制分析>>。第七步
骤严格意义上来说,不是可选的,而是必须的。

参看<<19.0 如何将stdin、stdout、stderr重定向到/dev/null>>。

A: W. Richard Stevens

一般我会使用类似daemon_init()这样的函数,使当前进程成为daemon进程。

--------------------------------------------------------------------------
static void daemon_init ( const char *workdir, mode_t mask )
{
int i, j;

/*
* change working directory, this step is optional
*/
chdir( "/tmp" );
if ( 0 != Fork() )
{
/*
* parent terminates
*/
exit( EXIT_SUCCESS );
}
/*
* first child continues
*
* become session leader
*/
setsid();
Signal( SIGHUP, SIG_IGN );
if ( 0 != Fork() )
{
/*
* first child terminates
*/
exit( EXIT_SUCCESS );
}
/*
* second child continues
*
* change working directory, chdir( "/" )
*/
chdir( workdir );
/*
* clear our file mode creation mask, umask( 0 )
*/
umask( mask );
j = Open( "/dev/null", O_RDWR );
Dup2( j, 0 );
Dup2( j, 1 );
Dup2( j, 2 );
j = getdtablesize();
for ( i = 3; i < j; i++ )
{
close( i );
}
return;
} /* end of daemon_init */
--------------------------------------------------------------------------

调用setsid(),如果成功,导致三个结果:

a. 创建一个新的session,当前进程成为session leader,也是新session中的惟一
进程。

b. 当前进程成为一个新进程组的组长(process group leader)。

c. 如果当前进程以前有一个控制终端,现在将脱离这个控制终端。

对于SVR4,一个session leader调用open()打开一个(伪)终端设备,如果这个终端不
是其它会话的控制终端,而open()时又未指定O_NOCTTY,则这个终端成为当前会话的
控制终端。第二次fork()后,孙子进程将确保不是session leader。于是以后不会再
有任何控制终端,彻底脱离。

必须在第二次fork()之前显式忽略SIGHUP信号。孙子进程将继承子进程所设置的信号
句柄。Stevens是这样解释的,当session leader终止时,系统会向该session中所有
进程分发SIGHUP信号。即这里的子进程终止时,系统会向孙子进程分发SIGHHUP信号。
前面有关于这个问题的讨论。

getdtablesize()返回的也就是sysconf( _SC_OPEN_MAX )返回的值。

D: scz <scz@nsfocus.com>

以FreeBSD 4.5-RELEASE为例进行讨论。

做为Guru of the Unix gurus,Andrew Gierth与Richard Stevens在各类文档或书籍
中对"进程"进行了相当广泛、深入的解释,其中可能引发困惑的一个问题是,父子进
程关系与信号分发的关系。

有相当多的人认为父进程终止时,子进程应该收到一个SIGHUP信号。即使熟练的Unix
程序员参与某些讨论时,也可能忘记几分钟前TA还在fork(),并立即让父进程退出的
事实。一般来说,有两种典型的与SIGHUP信号相关的情形。

假设某session有控制终端,当session leader终止时,系统会向该session前台进程
组中所有进程分发SIGHUP信号。

如果某进程组中有一个进程,其父进程属于同一会话(session)的另一个进程组,则
该进程组不是"孤儿进程组",反之该进程组称为"孤儿进程组"。

APUE 9.10指出,当某进程的终止导致一个新的"孤儿进程组"产生,系统会向这个新
的"孤儿进程组"中处于"停止"状态的每个进程分发SIGHUP信号,然后分发SIGCONT信
号。那些未处于"停止"状态的进程不会收到这两个信号。

启动"tcpdump -i lnc0 udp &",此时tcpdump成为后台进程组成员。退出当前shell,
此时tcpdump成为孤儿进程组成员,但它处于"运行"状态。重新登录后会发现该进程
仍然存在,它不是daemon进程,TT列不为??。它没有收到SIGHUP信号,手动kill -1
是可以杀掉它的。

启动"nohup tcpdump -i lnc0 udp",此时tcpdump仍为前台进程组成员。从另一shell
执行"kill -1"杀掉前一shell,此时tcpdump成为孤儿进程组成员。有SIGHUP信号分
发到tcpdump,因为session leader终止了。nohup确保tcpdump继续运行。对比没有
使用nohup时的情形。

有一个bindshell,它只是简单fork()了一次,父进程立即退出。并未处理SIGHUP信
号,也未调用setsid()。它已经达到目的了。fork()之后产生一个后台孤儿进程组,
并未脱离控制终端,但再也不会有SIGHUP信号分发到bindshell。前述两种情形都不
会出现。在控制终端上按Ctrl-C产生的SIGINT信号不会分发到后台进程组。一般入侵
中要的就是这个效果,并不需要复杂的daemon_init()。

还有一种情形,简单fork()一次,父进程调用setpgid()使子进程自己成为进程组长,
然后父进程退出。这只是确保产生后台孤儿进程组,setpgid()不是必须的。子进程
仍然过继给init进程。

两位先生给出的daemon化步骤考虑得相当周全。但更多的入侵者、临时跳板工具并不
需要daemon化,最省事的办法就是fork()一次。最后再强调一次,脱离控制终端、彻
底脱离控制终端与不受SIGHUP信号影响是两回事,绝大多数时候要的只是后者的效果。

此外,Linux可能在某些细节上与上述讨论有出入,但最后的结论一样,最省事的办
法就是fork()一次。

个人推荐严肃的Unix/C程序员在需要这类效果时,统一使用daemon_init(),并捕捉
相关信号。

D: lskuangren@bbs.apue.net 2003-07-11

FreeBSD和Linux直接提供了一个函数,DAEMON(3)

--------------------------------------------------------------------------
DAEMON(3) FreeBSD库函数手册 DAEMON(3)

名字

daemon - 在后台运行程序

标准C库(libc, -lc)

语法

#include <stdlib.h>

int daemon ( int nochdir, int noclose );

描述

daemon()用于脱离控制终端、转入后台运行程序(守护进程)。

如果第一形参nochdir为零,daemon()最终执行chdir( "/" )。

如果第二形参noclose为零,daemon()最终将stdin、stdout、stderr重定向到
/dev/null。

错误

失败时返回-1,并设置errno,errno的值与fork(2)、setsid(2)的情形一致。

参看

fork(2), setsid(2)

历史

4.4BSD首次引入了daemon()。
--------------------------------------------------------------------------

从man手册可以看出daemon()都做了些什么。在清楚自己到底需要何种效果的前提下,
可以不使用复杂的daemon_init()而直接使用daemon()。

AIX、Solaris未直接提供daemon(),如编写最广泛兼容程序,应避免使用daemon()。

24.4 将编译、链接过程分开

Q: 如下程序直接用gcc编译、链接无问题。但是将编译、链接过程分开时,失败。

$ gcc -Wall -pipe -O3 -c -o helloworld.o helloworld.c
$ ld -o helloworld helloworld.o
ld: warning: cannot find entry symbol _start; defaulting to 08048074

--------------------------------------------------------------------------
/*
* For x86/Linux 2.4.7-10/RedHat 7.2(gcc 2.96/gas 2.11.90.0.8)
* gcc -Wall -pipe -O3 -o helloworld helloworld.c
*/
#include <stdio.h>
#include <stdlib.h>

int main ( int argc, char * argv[] )
{
return( EXIT_SUCCESS );
} /* end of main */
--------------------------------------------------------------------------

A: scz <scz@nsfocus.com>

$ gcc -v -Wall -pipe -O3 -o helloworld helloworld.c &> gcc.txt
$ more gcc.txt

gcc.txt的内容真实反映了整个编译、链接过程,可简化成如下三个步骤:

$ gcc -Wall -pipe -O3 -S -o helloworld.s helloworld.c
$ as -Qy -o helloworld.o helloworld.s
$ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o helloworld helloworld.o /
/usr/lib/crt1.o /
/usr/lib/crti.o /
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtbegin.o /
-lc /
/usr/lib/gcc-lib/i386-redhat-linux/2.96/crtend.o /
/usr/lib/crtn.o

25. AIX相关问题

25.0 如何查看AIX版本号

Q: "uname -a"显示如下:

AIX aix4 3 4 001381144C00

可我想直接了当地看到AIX版本号

A: oslevel

4.3.3.0

25.1 如何在AIX命令行上修改IP地址

Q: 在/etc下用find搜索原来所配置的IP地址,结果只在/etc/hosts文件中找到一个
相关匹配,其它匹配是日志文件一类的。

find /etc -name "*" -type f | xargs grep <original ip>

于是我假设AIX跟Solaris一样只依赖/etc/hosts文件设置IP地址,修改该文件后
重启,发现新IP未生效,旧IP也不能正常工作。

A: 1) 正规命令行修改步骤如下:

smit tcpip
Further Configuration
Network Interfaces
Network Interface Selection
Change / Show Characteristics of a Network Interface
en0 Standard Ethernet Network Interface

2) 也可以用ifconfig直接修改IP地址,并放入启动脚本/etc/rc.net中。

ifconfig en0 192.168.7.250 netmask 255.255.255.0 broadcast 192.168.7.255 up

25.2 如何查看RS/6000物理内存大小

A: # lsattr -El sys0 -a realmem
realmem 262144 Amount of usable physical memory in Kbytes False

或者

# lsdev -Cc memory
mem0 Available 00-00 Memory
L2cache0 Available 00-00 L2 Cache
# lsattr -El mem0
size 256 Total amount of physical memory in Mbytes False
goodsize 256 Amount of usable physical memory in Mbytes False

25.3 AIX 4.3.3中"ls a*"不正常

Q: 某目录下简单执行ls,正常。执行"ls a*",报错:

ksh: /usr/bin/ls: 0403-027 The parameter list is too long.

原因是/usr/include/sys/limits.h里面ARG_MAX定义成24576。AIX 5可以修改这
个限制,AIX 4.3.3没有办法修改。

怎样达到原始目的,只显示当前目录(不包含其子目录)中以a开头的文件。

A: 至少有两种办法

#! /bin/sh
for file in `ls` ; do
if test -f $file ; then
if `/usr/bin/echo ${file} | /usr/bin/grep "^a.*" >/dev/null 2>&1` ; then
echo "${file}"
fi
fi
done

或者

ls -F | grep "^a.*[^/]$"

25.4 AIX多线程编程与errno全局变量

Q: AIX多线程编程中使用了全局变量errno,有什么需要注意的地方。

A: wayman@smth

使用gcc -D_THREAD_SAFE,此时errno是每个线程相关的,不再是全局变量。当正确地
包含<errno.h>之后,errno被重新定义过:

#if defined(_THREAD_SAFE) || defined(_THREAD_SAFE_ERRNO)
/*
* Per thread errno is provided by the threads provider. Both the extern
* int and the per thread value must be maintained by the threads library.
*/
#define errno (*_Errno())

#endif

extern int errno;

函数_Errno()返回一个指针,指向一个线程相关整数。注意,你仍然可以使用&errno。

在/usr/include/*.h中grep查看一下,会发现如果指定了-D_THREAD_SAFE,就不必再
指定-D_REENTRANT以及-D_THREAD_SAFE_ERRNO。

Unix编程/应用问答中文版(转)相关推荐

  1. unix编程书籍推荐

    [Unix下C/C++开发] unix编程书籍推荐 LiSteven 发布于 2年前,共有 0 条评论 Unix/Linux/BSD系统 相对于Windows,在UNIX下编程获得相关文档要方便很多. ...

  2. [Unix下C/C++开发] unix编程书籍推荐

    [Unix下C/C++开发] unix编程书籍推荐 发表于1年前(2012-12-20 10:14)   阅读(256) | 评论(0) 6人收藏此文章, 我要收藏 赞0 Unix/Linux/BSD ...

  3. linux/unix编程手册-61_64

    title: linux/unix编程手册-61_64 date: 2018-10-07 11:53:07 categories: programming tags: tips linux/unix编 ...

  4. 学习linux/unix编程方法的建议-转

    假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过  我想大概可以分为4个阶段,水平从低到高  从安装使用=>linux常用命令=>li ...

  5. Unix编程常见问题解答(精华)

    Unix编程常见问题解答 原帖:http://www.newsmth.net/bbsanc.php?path=%2Fgroups%2Fcomp.faq%2FUnix%2F3%2Ffaq%2FUnixP ...

  6. 三读《UNIX编程艺术》

                                              三读<UNIX编程艺术>           第一次读<UNIX编程 艺术>是2004年的夏 ...

  7. 学习Unix编程应该看的书籍整理

    最近看到网上的一段关于Unix底层工作方面的评论,深以为然: ============================================================ 底层个人觉得有两 ...

  8. 【转载】Unix编程艺术——Unix哲学

    Unix编程艺术主要介绍了Unix系统领域中的设计和开发哲学.思想文化体系.原则与经验.此文大致摘录了Unix的哲学. web程序员也应该看看此书,软件开发和web开发很多地方都是相通的.看完之后,我 ...

  9. Unix编程艺术——Unix哲学

    题外话:一个月之前,刚刚读完<unix编程艺术>这本据说有些"愤青"的书,读书的过程,感觉是种享受,不但unix本身是艺术,连作者著书也是艺术,本想就本书写篇读感,但是 ...

最新文章

  1. Spark任务提交底层原理
  2. 10 种机器学习算法的要点
  3. 【解决方案】chrome打开新标签页自动打开chrome://newtab并且跳转到谷歌香港
  4. UE3客户端加入DS过程
  5. Mybatis传递参数的三种方式
  6. java抽取注释_JAVA 注解教程(五)注解的提取
  7. asp.net 递归删除文件夹及其子文件夹和所有文件[转]
  8. Linux Mint 20.1 “Ulyssa” 将于 12 月中旬发布
  9. UTF-8字符集成为Java 18默认字符集?发布周期将至,Java 18现身
  10. 与孩子一起学编程04章
  11. eclipse汉化完成常见问题的解决方案
  12. kaggle数据集下载-搜狐浏览器
  13. C语言三种常见的输出格式
  14. si24r1程序_SI24R1多对一通信功能(ACK模式,ACK不带PAYLOAD)程序资料开发
  15. 学习java能干什么?学习java能做哪些工作?
  16. SEO优化:如何挖掘谷歌关键词-实例讲解(2022最新)
  17. 解决谷歌Chrome浏览器默认主页被毒霸篡改的问题
  18. 你想要的宏基因组-微生物组知识全在这(19国庆特别篇)
  19. python3.8零基础入门教程_正版 Python 3.8编程快速入门 针对wan全零基础入门的读者 采用*小化安装+极简代码的教学...
  20. 几种常用的特征选择方法

热门文章

  1. 家庭NAS服务器(3)VM测试-固态缓存盘加速机械盘
  2. MATLAB添加工具包(详细)
  3. ASO优化:iOS如何做好ASO推广
  4. 【解决Maven项目module不显示蓝色小方块/java文件显示灰色橙色/module already exists问题】
  5. 用Python写中文数字对照表
  6. 网线传输速度测试_网络带宽和速度测试windows和linux用iperf工具
  7. 女生要不要去北航学计算机,在北航读书有个女朋友是种怎样的体验?
  8. subprocess用法,官方文档
  9. 下载文件时,文件名乱码。
  10. python提前查成绩_利用Python来查询自己的成绩!想改成绩吗?我教你啊!