-------------------------------------------------------------------------- 2005-12-10 12:40 scz 最近几年工作重心转向Windows平台,但这份文档仍在维护之中,不会终止。当然, 更新率会越来越低,直至某天工作重心再次转回Unix平台,谁知道呢,:-) 名称 -- Unix编程/应用问答中文版(2005-12-10外发版) 链接 -- https://scz.617.cn/unix/200401170955.txt 维护 -- 小四 主页 -- http://www.nsfocus.com 创建 -- 2001-02-05 13:49 更新 -- 2005-12-07 08:39 感谢 -- 感谢C语言的发明者、Unix操作系统的发明者、感谢全世界C程序员创造的Unix共 享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。 感谢deepin,在早期维护过程中的支持、帮助和鼓励。感谢所有NSFOCUS安全研 究小组(security@nsfocus.com)的朋友。 主要支持人员(字母顺序) -- Andrew Gierth backend Casper H.S. Dik deepin jbtzhm law scz suxm tt 简介 -- 这份文档不是FAQ(Frequently Answered Question),不少问题属于FUQ(Freque- ntly Unanswered Question)。换句话说,不一定是最常见的编程、应用问答,很可 能其中的答案尚是一个构思,还没有成为现实,又或者根本是个错误的思想火花。但 是,她的确在试图回答一些很有意义的问题,让更多的Unix/C程序员、系统管理员共 享彼此的智慧,那是三十年前无数前辈精英做到过的,也是我们正试图做到的。 Q -- Question A -- Answer D -- Discuss 声明 -- 一直没有单独出一份完整的,原因很多。如果搁在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如何在命令行上增加新用户 1.13 如何不让Solaris自动休眠 1.14 Solaris OS Guide for New System Administrators 1.15 粘滞位的意义 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的确切含义是什么 10.18 Linux下网卡重命名 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 如何查看系统当前glibc版本 16.9 Solaris 8下如何配置运行时链接环境 16.10 libcrypto.so.0.9.6是什么软件包里的 16.11 共享库的动态加载/卸载 16.12 编译时命令行指定-ldl,ldd观察时却是libdl.so.2,为什么 16.13 如何进行部分静态链接 16.14 如何知道一个运行中的进程加载了哪些动态链接库 16.15 制作so文件时如何指定引出哪些符号 17. 文件查看问题 17.0 如何改变vi临时目录 17.1 如何直接查看man文件 17.2 .tex文件怎么读 17.3 Solaris下怎么看.ps文件 17.4 如何将man手册转换成文本文件以便查看 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 19.7 如何让终端显示从黑方块状态恢复成正常状态 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 批量文件字符串替换 20.14 如何显示当前目录下除./与../之外的所有文件、子目录名 20.15 如何使nohup不输出 20.16 如何在csh的命令行提示符中包含当前路径信息 20.17 如果一行含有不以foo前导的bar,则满足需求 20.18 如何得知正在使用什么shell 20.19 如何删除字符串的最后一个字符 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 ) )报错 23.8 unknown terminal "vt100" 24. Unix编程相关问题 24.0 如何知道fd是有效文件句柄 24.1 如何使代码段可写 24.2 建议性文件锁与强制性文件锁 24.3 如何编写daemon程序 24.4 将编译、链接过程分开 24.5 进程如何分辨谁在kill()自己 24.6 getopt()、getopt_long()、getopt_long_only()是何用法 24.7 能否在connect()之前得到本地端口号 24.8 如何在GB2312与Unicode之间互相转换 24.9 printf()时想显示文件名及行号 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全局变量 25.5 AIX如何进入单用户模式 -------------------------------------------------------------------------- 0. Unix/C传奇问题 0.1 Dennis Ritchie 和 Ken Thompson Q: 我想知道他们,为什么大家不断提到这两个名字? A: All of Unix Programmers 我们也想知道,:-P 1969年Dennis Ritchie和Ken Thompson在贝尔实验室创造性地发明了Unix操作系统, 为此1983年他们获得了图灵奖。 尽管Ritchie是C程序设计语言的发明者,但是他最喜欢的编程语言是Alef。而 Thompson是一名业余飞行员,曾到莫斯科驾驶过米格-29。 欢迎访问 http://cm.bell-labs.com/who/dmr/ http://cm.bell-labs.com/who/ken/ 0.2 W. Richard Stevens 之死 Q: David Johns 我是他的崇拜者,用www.google.com搜索他的讣告,但这份讣告没有提及死因,有人 知道吗? 真的仅仅是英年早逝吗? A: Nithyanandham 他死于1999/09/01,家人不想让别人知道死因。讣告位于 http://www.azstarnet.com/clips/richard_stevens.html A: joe broz 似乎是一场攀岩事故,或者滑雪事故,我不确认。 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/2000/09/01/rich_stevens/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@APUE 2002-04-08 15:54 搞笑片段 我现在越来越崇拜Stevens了,因为昨天我看的电影--反斗神鹰(hot shot)--的导演 是Richard Stevens。 D: Rachel Chalmers 这个可与之比拟,不过这个是真的: 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.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|24/\/\/\/\3|2" 更多信息参看如下链接 http://www.cwru.edu/orgs/sigmataudelta/submissions/rome-relaxweunderstand.htm D: dfbb@SMTH 有个geekcode也差不多,http://www.geekcode.com/geek.html 1. 系统管理配置问题 1.0 如何屏蔽power-button Q: 如何屏蔽Sun键盘右上角的power-button A: Alan Coopersmith 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 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 启动时Stop-N可以恢复OBP缺省设置,应该可以把这个密码去掉吧 按住Stop-N,加电,直到键盘灯闪 D: lose@SMTH 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 你无法改变分区大小,但是可以增加/删除交换文件,效果类似交换分区。下列命令 在根目录下创建一个500MB的交换文件,名为swapfile # mkfile 500m /swapfile 下列命令将使之生效 # swap -a /swapfile 现在你有了额外的500MB交换空间,为了每次重启后依旧有效,编辑/etc/vfstab文件 增加如下行 /swapfile - - swap - no - # swap -l 这里"-l"意味着"list",显示所有交换空间。仔细阅读"swap"和"mkfile"的手册页。 1.3 为什么我不能在/home目录下创建子目录 Q: Solaris 7下,root身份,当我试图在/home目录下创建子目录时,系统拒绝,为什么? A: mohansundarraj 如果/etc/rc2.d/S74autofs脚本中automount(1M)守护进程已经mount了/home,就是 这种现象,而这还是缺省安装后的情形。可以 # /etc/init.d/autofs stop # umount /home 然后你就可以用root身份在/home下创建子目录,增加文件了。为了永久取消autofs 特性,可以将/etc/rc2.d/S74autofs脚本改名,并注释掉/etc/auto_home、 /etc/auto_master两个文件中的入口点。 SPARC/Solaris的缺省用户主目录是/export/home,而不是/home。 1.4 如何改变一台主机的locale Q: 一台SPARC/Solaris 8运行在US locale环境中,现在我们想让它运行在 IE(Ireland) locale环境中,以便可以使用欧洲日期格式,怎么办? A: Sharad Ramachandran 运行sys-unconfig,在此之前请man -s 1M sys-unconfig,:-) A: chad schrock 天啊,为了拍死一只苍蝇,你要引爆原子弹吗? 只需要做如下操作,在你的.cshrc/.profile/.bashrc等启动脚本中设置$LANG环境变 量的值为en_UK,注销,重新登录即可。为了使这个设置全局有效,修改 /etc/default/init文件,LANG=en_UK,重启动。 -------------------------------------------------------------------------- # @(#)init.dfl 1.2 92/11/26 # # This file is /etc/default/init. /etc/TIMEZONE is a symlink to this file. # This file looks like a shell script, but it is not. To maintain # compatibility with old versions of /etc/TIMEZONE, some shell constructs # (i.e., export commands) are allowed in this file, but are ignored. # # Lines of this file should be of the form VAR=value, where VAR is one of # TZ, LANG, or any of the LC_* environment variables. # TZ=GMT+8 LANG=zh.GBK -------------------------------------------------------------------------- 参看locale(1)和locale(5),了解更多关于locale的信息。运行"locale -a",查看 当前系统所支持的所有locale。 A: Sun Microsystems 2001-06-12 有三种方式改变locale。首先用"locale -a"命令确认系统中已安装的locale 1) 从CDE登录屏幕上修改locale 选择 options -> languages -> choose the new locale 注意,如果登录用户的初始化文件中有不同的locale设置,将优先于系统全局locale 设置。 2) 临时设置locale(shell相关的) ksh : LANG= sh : LANG= export LANG csh : setenv LANG bash: export LANG=en_US(zh.GBK) 3) vi /etc/default/init 增加如下内容 LANG= LC_ALL= 重启系统。 运行"locale"命令确认改变生效。 如果你希望使用的locale并未安装,参看如下文档安装locale Solaris 8 : <> Solaris 7 : <> Solaris 2.6: <> D: scz 1998-08 SPARC/Solaris 2.5下,为了在vi中正确看到中文需要设置环境变量 sh LANG=C;export LANG LC_CTYPE=iso_8859_1;export LC_CTYPE csh setenv LANG zh 关于设置LANG这个环境变量涉及到/usr/lib/locale下的目录权限。 1.5 Solaris 7自动注销 Q: 怎样设置才能30秒后自动注销 A: shridhara 不幸的是,Solaris对此没有什么好的支持。如果正在使用telnet会话,或许可以考 虑"logout"变量,参看telnet的手册页。一个变通的办法,使用K-Shell,它支持 TMOUT变量,用于指定非活动时限(以秒为单位)。比如,如果一个shell会话3分钟内 不活动,则终止这个shell会话 $ TMOUT=180;export TMOUT 可以在用户的.profile文件中放置该行。缺点是你只能使用ksh。 D: quack Linux、Solaris 2.6上的bash试了也行。 D: scz vi /etc/default/login # TIMEOUT sets the number of seconds (between 0 and 900) to wait before # abandoning a login session. # TIMEOUT=180 这里的超时设置针对登录过程,而不是登录成功后的shell会话超时设置。 1.6 一个目录拥有setgid设置,怎么理解 Q: 对一个目录做了setgid设置,可我并没有发现这和正常情况有什么区别 A: John Riddoch 在这种目录下创建新文件时将采用setgid设置对应的属组,比如 $ ls -ld b drwxrws--- 2 jr group 512 Mar 14 17:13 b/ $ touch b/a $ ls -l b/a -rw------- 1 jr group 0 Mar 14 17:13 b/a $ id uid=178(jr) gid=10(staff) jr的缺省组是staff,而现在b/a文件属组是group。 D: 小四 SPARC/Solaris 7下测试 如果目录拥有SGID设置,那么该目录下新创建的文件将继承该目录的属组,而不是创 建者所对应的GID。 [root@ /export/home/scz]> id uid=0(root) gid=1(other) <-- 注意当前用户的属组 [root@ /export/home/scz]> mkdir groupsgid [root@ /export/home/scz]> ls -ld groupsgid drwxr-xr-x root other groupsgid/ [root@ /export/home/scz]> chown scz:users groupsgid [root@ /export/home/scz]> chmod g+s groupsgid [root@ /export/home/scz]> ls -ld groupsgid drwxr-sr-x scz users groupsgid/ <-- 目录拥有SGID设置 [root@ /export/home/scz]> cd groupsgid/ [root@ /export/home/scz/groupsgid]> touch scz_0 [root@ /export/home/scz/groupsgid]> ls -l scz_0 -rw-r--r-- root users scz_0 <-- 注意属组变化 [root@ /export/home/scz/groupsgid]> chmod g-s ../groupsgid/ [root@ /export/home/scz/groupsgid]> ls -ld ../groupsgid/ drwxr-xr-x scz users ../groupsgid/ [root@ /export/home/scz/groupsgid]> touch scz_1 [root@ /export/home/scz/groupsgid]> ls -l scz_1 -rw-r--r-- root other scz_1 <-- 注意属组变化 [root@ /export/home/scz/groupsgid]> 1.7 非Sun Console上有无等价Stop-A的按键 A: neomilev 如果是便携机,尝试alt/break 或者 ctrl/break。如果是vt100终端,尝试F11 或者 break 1.8 如何让一个用户只能ftp而无法telnet A: 小四 修改该用户在/etc/passwd中的shell为/bin/false,在/etc/shells文件中增加 /bin/false,此时,该用户只能ftp,telnet失败。 如果/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 有三种办法 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 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 -g other -d /export/home/ -s /usr/bin/bash -c -m 1.13 如何不让Solaris自动休眠 Q: Solaris有时会自动休眠,连电源都关了,然后按Sun键盘右上角的power-button,就 开始恢复到休眠以前的状态。我想屏蔽这个特性。 A: 1997-07 参看power.conf(4)、pmconfig(1M)手册页。最简单的办法是编辑/etc/power.conf, 注释掉autoshutdown那一行,然后执行pmconfig使之立即生效。 1.14 Solaris OS Guide for New System Administrators A: Rolf Kersten [2004-06-25] http://www.sun.com/bigadmin/content/solSysadminGuide/ http://www.sun.com/bigadmin/content/solSysadminGuide/solsysadmin.pdf 1.15 粘滞位的意义 A: Linux stat(2): 当一个目录设置了sticky bit(S_ISVTX)时,意味着该目录下的文件仅当下述条件之 一满足时可被重命名、删除: 1) 文件属主 2) 目录属主 3) root Solaris chmod(1): 只有root才能对非目录的普通文件设置粘滞位。 Solaris chmod(2): 当一个目录可写并且设置了粘滞位时,该目录下的文件仅当下述条件之一满足时可被 重命名、删除(rename(2)、unlink(2)): 1) 文件属主 2) 目录属主 3) 对该文件拥有写权限的用户 4) root 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 SIGBUS与SIGSEGV信号的一般区别如下: 1) SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该 指针。通常是未对齐的数据访问所致。 2) SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对 应该地址。 A: scz 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 #include 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
: save %sp, -128, %sp 0x10814 : st %i0, [ %fp + 0x44 ] 0x10818 : st %i1, [ %fp + 0x48 ] 0x1081c : sethi %hi(0x12345400), %o1 0x10820 : or %o1, 0x278, %o0 ! 0x12345678 0x10824 : st %o0, [ %fp + -20 ] 0x10828 : clr [ %fp + -24 ] 0x1082c : add %fp, -20, %o0 0x10830 : st %o0, [ %fp + -28 ] 0x10834 : ld [ %fp + -28 ], %o0 0x10838 : clrb [ %o0 ] 0x1083c : ld [ %fp + -28 ], %o0 0x10840 : add %o0, 1, %o1 0x10844 : st %o1, [ %fp + -24 ] 0x10848 : ld [ %fp + -24 ], %o0 0x1084c : clrh [ %o0 ] 0x10850 : clr %i0 0x10854 : b 0x1085c 0x10858 : nop 0x1085c : ret 0x10860 : 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进行优化,但仍然使用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 #include 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
: save %sp, -120, %sp 0x10814 : st %i0, [ %fp + 0x44 ] 0x10818 : st %i1, [ %fp + 0x48 ] 0x1081c : sethi %hi(0x12345400), %o1 0x10820 : or %o1, 0x278, %o0 ! 0x12345678 0x10824 : st %o0, [ %fp + -20 ] 0x10828 : clrh [ %fp + -22 ] 0x1082c : lduh [ %fp + -19 ], %o0 0x10830 : sth %o0, [ %fp + -22 ] 0x10834 : clr %i0 0x10838 : b 0x10840 0x1083c : nop 0x10840 : ret 0x10844 : restore End of assembler dump. (gdb) i r pc pc 0x1082c 67628 (gdb) 因此在SPARC架构上编程,一定要留神强制类型转换,务必清楚自己正在干什么,有 没有隐患。 D: yuhuan@SMTH 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 在代码段绝对地址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 用/usr/proc/bin/pstack [-F] 参看这个例子代码,http://home1.stofanet.dk/breese/debug/debug.tar.gz Q: is there a way to access call stack information at run time from within a program? i've been maintaining my own crude stack using __FUNCTION__ and linked lists but can't help but think there's gotta be a better way... A: Nate Eldredge 这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数, 参看,其他系统可能有不同的技术支持。 注意,你所使用的办法可能是唯一能够保证跨平台使用的 A: Andrew Gabriel Consultant Software Engineer 下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那么这 个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,好像 Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时间了。 /* * Produce a stack trace for Solaris systems. * * Copyright (C) 1995-1998 Andrew Gabriel * Parts derived from Usenet postings of Bart Smaalders and Casper Dik. * */ /* ......................................................................... */ #include #include #include #include #include #include #include #include #if defined(sparc) || defined(__sparc) #define FLUSHWIN() asm("ta 3"); #define FRAME_PTR_INDEX 1 #define SKIP_FRAMES 0 #endif #if defined(i386) || defined(__i386) #define FLUSHWIN() #define FRAME_PTR_INDEX 3 #define SKIP_FRAMES 1 #endif #if defined(ppc) || defined(__ppc) #define FLUSHWIN() #define FRAME_PTR_INDEX 0 #define SKIP_FRAMES 2 #endif /* ......................................................................... */ static void print_address ( void * pc ) { Dl_info info; if ( dladdr( pc, &info ) == 0 ) { /* not found */ fprintf( stderr, "*** %s:0x%x\n", "??", ( unsigned int )pc ); } else { /* found */ fprintf( stderr, "*** %s:%s+0x%x\n", info.dli_fname, info.dli_sname, ( unsigned int )pc - ( unsigned int )info.dli_saddr ); } return; } /* end of print_address */ /* ......................................................................... */ static int validaddr ( void * addr ) { static long pagemask = -1; char c; if ( pagemask == -1 ) { pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 ); } addr = ( void * )( ( long )addr & pagemask ); if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM ) { return 0; /* invalid */ } else { return 1; /* valid */ } } /* end of validaddr */ /* ......................................................................... */ /* * this function walks up call stack, calling print_addess * once for each stack frame, passing the pc as the argument. */ static void print_stack ( void ) { struct frame * sp; jmp_buf env; int i; int * iptr; FLUSHWIN(); setjmp( env ); iptr = ( int * )env; sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ]; for ( i = 0; i < SKIP_FRAMES && sp; i++ ) { if ( !validaddr( sp ) || !validaddr( &sp->fr_savpc ) ) { fprintf( stderr, "***[stack pointer corrupt]\n" ); return; } sp = ( struct frame * )sp->fr_savfp; } i = 100; /* looping check */ while ( validaddr( sp ) && validaddr( &sp->fr_savpc ) && sp->fr_savpc && --i ) { print_address( ( void * )sp->fr_savpc ); sp = ( struct frame * )sp->fr_savfp; } } /* end of print_stack */ /* ......................................................................... */ void backtrace( void ) { fprintf( stderr, "***backtrace...\n" ); print_stack(); fprintf( stderr, "***backtrace ends\n" ); } /* ......................................................................... */ 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 看看mpatrol的源代码,其中有traceback()函数可以给出整个调用栈回溯,而不仅仅 是主调函数。 D: Peter Ammon 可以考虑使用宏,这是一个例子 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -o test test.c */ #include #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 #include #include #include #include #include #include #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 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 AIX 4.3.3.0 栈底是0x2FF23000 D: jonah 对于NetBSD 1.5,栈底是0xBFC00000。根据源码,最高用户地址是0xBFBFE000,因为 最后4MB(2^22)的最后两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再 使用这块内存。因此,0xBFBFE000才是真正的栈底。 tt在OpenBSD 2.8上测试结果,栈底是0xDFBFE000,注意和NetBSD 1.5相差很大。 A: tt -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o gstack gstack.c * * A simple example to get the current stack bottom address * warning3 * 2001-06-01 * * Modified by scz * 2001-06-02 */ #include #include #include #include #include /* * for signal handlers */ typedef void Sigfunc ( int ); static char * get_stack_bottom ( void ); static Sigfunc * PrivateSignal ( int signo, Sigfunc * func ); static void segfault ( int signo ); static Sigfunc * Signal ( int signo, Sigfunc * func ); static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0; static Sigfunc *seg_handler; /* * for xxxBSD */ static Sigfunc *bus_handler; static char * get_stack_bottom ( void ) { /* * for autovar, must be volatile */ volatile char *c; 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 ); } /* * now sigsetjump() is OK */ canjump = 1; while ( 1 ) { *c = *c; c++; } return( NULL ); } /* end of get_stack_bottom */ static Sigfunc * PrivateSignal ( 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 PrivateSignal */ static void segfault ( int signo ) { if ( 0 == canjump ) { /* * unexpected signal, ignore */ return; } canjump = 0; /* * jump back to main, don't return */ siglongjmp( jmpbuf, signo ); } /* end of segfault */ /* * for our signal() function */ static Sigfunc * Signal ( int signo, Sigfunc * func ) { Sigfunc * sigfunc; if ( SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) ) { exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */ 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 2001-06-03 00:38 W. Richard Stevens在<>中详细 介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。 这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV 信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。 tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD, NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV 信号。 非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号 句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此 时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句 柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了 保存恢复,有些系统没有做,比如x86/Linux Kernel 2.4.7-10的setjmp/longjmp没 有做信号屏蔽字的保存恢复。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函 数。下面是来自SPARC/Solaris 7的setjmp(3C) -------------------------------------------------------------------------- #include int setjmp ( jmp_buf env ); int sigsetjmp ( sigjmp_buf env, int savemask ); void longjmp ( jmp_buf env, int val ); void siglongjmp ( sigjmp_buf env, int val ); -------------------------------------------------------------------------- 如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回 来的时候从env中恢复信号屏蔽字。 数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有 虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是 与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。 在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也 保持不变。 无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom() 中声明c为volatile变量。 注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、 SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指 令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一 次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用 长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果 在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。 D: scz 2001-06-03 00:40 在x86/Linux系统中用如下命令可以确定栈区所在 # cat /proc/1/maps <-- 观察1号进程init ... ... bfffe000-c0000000 rwxp fffff000 00:00 0 # 在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在 # /usr/proc/bin/pmap 1 <-- 观察1号进程init ... ... FFBEC000 16K read/write/exec [ stack ] # 16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000 与前面tt介绍的 SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 ) 相符合。 此外,在SPARC/Solaris 7下,可以这样验证之 # /usr/ccs/bin/nm -nx /dev/ksyms | grep "|_userlimit" [7015] |0x0000100546f8|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit [8051] |0x000010054700|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit32 # echo "_userlimit /J" | adb -k /dev/ksyms /dev/mem physmem 3b72 _userlimit: _userlimit: ffffffff80000000 # skd64 0x000010054700 8 byteArray [ 8 bytes ] ----> 0000000000000000 00 00 00 00 FF BF 00 00 # ~~~~~~~~~~~ 对于32-bit应用程序来说,这是用户 空间上限 如果编译64-bit应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000 # /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o gstack gstack.c # ./gstack Current stack bottom is at 0xffffffff80000000 # 对于SPARC/Solaris 2.6 32-bit kernel mode # echo "_userlimit /X" | adb -k /dev/ksyms /dev/mem physmem 3d24 _userlimit: _userlimit: f0000000 # Q: 在x86/Linux平台上如何定位栈区(stack)的栈底(高址)与栈顶(低址)位置。 D: "Andrew Gabriel" 试试getcontext(2) A: "Shaun Clowes" 检查/proc//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 这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我 不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN 和BSD 4.3都支持它。 为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用: ptrace( PTRACE_TRACEME, 0, 0, 0 ); 接下来调用exec()家族的函数执行你最终企图跟踪的程序。 为了单步进入子进程,在父进程中调用: ptrace( PTRACE_SINGLESTEP, 0, 0, 0 ); 还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。 GDB的源代码足以回答这个问题。 2.7 x86/Linux上如何处理SIGFPE信号 Q: 参看如下程序 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c * * 注意与下面的编译效果进行对比,去掉优化开关-O3 * * gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c */ #include #include #include #include #include #include /* * for signal handlers */ typedef void Sigfunc ( int ); Sigfunc * signal ( int signo, Sigfunc *func ); static Sigfunc * Signal ( int signo, Sigfunc *func ); static void on_fpe ( int signo ); Sigfunc * signal ( int signo, Sigfunc *func ) { struct sigaction act, oact; act.sa_handler = func; sigemptyset( &act.sa_mask ); act.sa_flags = 0; if ( signo == SIGALRM ) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */ #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( SIG_ERR ); } return( oact.sa_handler ); } /* end of signal */ static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc; if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR ) { perror( "signal" ); exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */ static void on_fpe ( int signo ) { fprintf( stderr, "here is on_fpe\n" ); return; } /* end of on_fpe */ int main ( int argc, char * argv[] ) { unsigned int i; Signal( SIGFPE, on_fpe ); i = 51211314 / 0; /* * 另外,增加这行后,再次对比有-O3和无-O3的效果 * * fprintf( stderr, "i = %#X\n", i ); */ return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 有-O3、无-O3,以及有无最后那条fprintf()语句,效果上有差别,自行对比。如果 输出"here is on_fpe",则会发现永不停止。 D: 小四 2001-12-14 18:25 在上述代码中,on_fpe()直接返回了,再次触发除零错,所以无休止输出。事实上在 所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的时 候又碰上过。正确的做法是,利用远跳转转移,让开触发除零错的代码。 代码修改如下 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c * * 注意与下面的编译效果进行对比,去掉优化开关-O3 * * gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.c */ #include #include #include #include #include #include /* * for signal handlers */ typedef void Sigfunc ( int ); Sigfunc * signal ( int signo, Sigfunc *func ); static Sigfunc * Signal ( int signo, Sigfunc *func ); static void on_fpe ( int signo ); static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0; Sigfunc * signal ( int signo, Sigfunc *func ) { struct sigaction act, oact; act.sa_handler = func; sigemptyset( &act.sa_mask ); act.sa_flags = 0; if ( signo == SIGALRM ) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */ #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( SIG_ERR ); } return( oact.sa_handler ); } /* end of signal */ static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc; if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR ) { perror( "signal" ); exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */ static void on_fpe ( int signo ) { if ( canjump == 0 ) { return; /* unexpected signal, ignore */ } canjump = 0; fprintf( stderr, "here is on_fpe\n" ); siglongjmp( jmpbuf, signo ); /* jump back to main, don't return */ return; } /* end of on_fpe */ int main ( int argc, char * argv[] ) { unsigned int i; if ( sigsetjmp( jmpbuf, 1 ) != 0 ) { fprintf( stderr, "c u later\n" ); return( EXIT_SUCCESS ); } /* * now sigsetjump() is OK */ canjump = 1; Signal( SIGFPE, on_fpe ); i = 51211314 / 0; /* * 另外,增加这行后,再次对比有-O3和无-O3的效果 * * fprintf( stderr, "i = %#X\n", i ); */ return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 关于-O3的讨论,对gcc编译器熟悉的朋友请继续,呵,我对Linux下的这此东西,实 在缺乏兴趣。 2.8 GDB调试时没有符号表,如何设置断点 Q: 在OpenBSD 3.0下想用gdb跟踪/usr/bin/skeyaudit(一个setuid-to-root程序), 却因没有符号表无法设置断点 A: tt # 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 to continue, or q 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编译选 项)。 A: Sun Microsystems 2000-05-15 检查可执行文件中是否包含".stab" elf section,".stab" section用于保存相关调 试信息。 下面这个脚本演示如何判断可执行文件是否携带调试信息 -------------------------------------------------------------------------- #! /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 " 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 -------------------------------------------------------------------------- D: scz@nsfocus 如果对ELF文件格式不熟悉,理解上述代码可能有点困难,参看: http://www.digibel.org/~tompy/hacking/elf.txt http://www.muppetlabs.com/~breadbox/software/ELF.txt 这是1.1版的ELF文件格式规范。 一般随着".stab"节的出现,还会出现".stabstr"节。似乎本办法只适用于Solaris, 不适用于Linux?在Linux上测试时,要么是无论如何都不出现".stab"节,要么是无 论如何都出现".stab"节,见鬼。我是用"objdump -h"测试的。 3.2 mprotect如何用 A: 小四 下面是SPARC/Solaris 2.6中的mprotect(2)手册页 -------------------------------------------------------------------------- 系统调用 mprotect(2) 名字 mprotect - 设置内存权限 摘要 #include int mprotect ( void *addr, size_t len, int prot ); 描述 mprotect()设置范围[addr, addr + len),一个左闭右开区间。len 将向上舍入到页大小的整数倍。可以利用sysconf(3C)获取页大小。 形参prot指定内存权限。prot的合法取值与mmap()使用的一致,定义 在文件中 PROT_READ /* page can be read */ PROT_WRITE /* page can be written */ PROT_EXEC /* page can be executed */ PROT_NONE /* page can not be accessed */ 如果mprotect()失败原因不是EINVAL,则可能出现[addr, addr + len) 中的部分页面内存权限修改成功,而另一部分修改失败。比如从addr2 开始设置权限失败,则[addr, addr2]之间的页面设置权限成功 返回值 成功返回0。否则返回-1,检查errno。 错误码 EACCES The prot argument specifies a protection that violates the access permission the process has to the underlying memory object. EINVAL 形参len小于等于0,或者形参addr不在页边界上 ENOMEM [addr, addr + len)不是进程的合法地址空间,或 者指定了一个或多个尚未映射过的页面 EAGAIN [addr, addr + len)包含了一个或多个页面,这些 页面在内存中被锁定并且是以MAP_PRIVATE方式映射 的,形参prot带有PROT_WRITE设置,系统没有足够 资源用于可能创建的私有页面。如果对以前不可写 而现在可写的(刚设置成可写的)地址范围进行写操 作,就会创建私有页面(写时复制,copy-on-write)。 参看 mmap(2)、plock(3C)、mlock(3C)、mlockall(3C)、sysconf(3C) -------------------------------------------------------------------------- # 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 #include #include #include int main ( int argc, char * argv[] ) { char *buf; char c; /* * 分配一块内存,拥有缺省的rw-保护 */ buf = ( char * )malloc( 1024 + 8191 ); if ( !buf ) { perror( "malloc" ); exit( errno ); } /* * Align to a multiple of PAGESIZE, assumed to be a power of two */ buf = ( char * )( ( ( unsigned int )buf + 8191 ) & ~8191 ); c = buf[77]; buf[77] = c; printf( "ok\n" ); /* * Mark the buffer read-only. * * 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数 */ if ( mprotect( buf, 1024, PROT_READ ) ) { perror( "\nmprotect" ); exit( errno ); } c = buf[77]; /* * Write error, program dies on SIGSEGV */ buf[77] = c; exit( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ ./mtest ok 段错误 (core dumped) <-- 内存保护起作用了 $ 3.3 mmap如何用 A: 小四 下面是SPARC/Solaris 2.6中的mmap(2)手册页 -------------------------------------------------------------------------- 系统调用 mmap(2) 名字 mmap - map pages of memory 摘要 #include void * mmap ( void *addr, size_t len, int prot, int flags, int fildes, off_t off ); 描述 mmap()在进程地址空间和一个虚拟内存对象之间建立一种映射关系 pa = mmap( addr, len, prot, flags, fildes, off ); fildes是一个文件句柄,off是文件内偏移,len指明映射多少字节。 返回值pa是实现相关的一个值。地址范围[pa, pa + len)和文件偏移 范围[off, off + len)之间存在映射关系。 概念上,[pa, pa + len)可以越过文件尾,比如文件是新创建的或者 被截断过。但是不能引用越过文件尾的地址,这会导致分发SIGBUS、 SIGSEGV信号。换句话说,mmap()不能用于扩展文件长度。 在地址范围[pa, pa + len)上,后续的mmap()将替代前面的mmap()。 映射建立后,close(2)文件句柄并不会导致映射关系自动拆除。应该 使用munmap(2)拆除映射关系。 prot形参指明映射页面的内存权限,参看中的定义 PROT_READ Page can be read. PROT_WRITE Page can be written. PROT_EXEC Page can be executed. PROT_NONE Page can not be accessed. PROT_WRITE 经常实现成rw-,PROT_EXEC则对应r-x。如果PROT_WRITE 未被设置,肯定不可写。 flags形参也定义在文件中 MAP_SHARED Share changes. MAP_PRIVATE Changes are private. MAP_FIXED Interpret addr exactly. MAP_NORESERVE Don't reserve swap space. 如果指定 MAP_SHARED,内存写操作立即为映射过同一文件的其他进 程所察觉。如果指定 MAP_PRIVATE,第一次内存写操作导致写时复制 (copy-on-write),创建一个私有页面,映射过同一文件的其他进程 无法察觉这次及其后续的内存写操作。注意,创建私有页面之前,以 MAP_SHARED方式映射过同一文件的其他进程的写操作将立即为当前进 程所察觉。必须指定 MAP_SHARED 或者 MAP_PRIVATE,但不能同时指 定。fork()后这种设置依旧有效。 译注:指定MAP_SHARED才可能使写操作最终影响对象/文件,指 定MAP_PRIVATE始终无法使写操作最终影响对象/文件,所 做修改局限于内存区域,即使调用msync()也无意义。 指定MAP_SHARED,意味着内存读/写对应对象/文件I/O。 指定MAP_PRIVATE,意味着仅仅是内存读/写。 如果指定 MAP_FIXED,返回值pa等于形参addr,不鼓励指定 MAP_FIXED,这可能导致无法最高效地利用系统资源。 如果指定 MAP_FIXED 的同时,形参addr与前次映射返回的pa相等, 则首先拆除前次映射关系,然后建立新的映射关系。 如果未指定 MAP_FIXED ,分两种情况。如果形参addr不为零,系统 参照形参addr、len自行挑选addr附近的最终映射地址pa。如果形参 addr为零,则系统任意挑选pa,但绝不会在地址0处开始映射,不会 替代任何现存映射,也不会映射到被认为是数据段(data)、堆栈段 (stack)一部分的内存区域上去。 如果不指定 MAP_NORESERVE,以 MAP_PRIVATE 方式进行可写映射时, 系统保留空间用于可能创建的私有页面,发生写操作时保留空间被用 于创建私有页面。指定 MAP_NORESERVE 以 MAP_PRIVATE 方式进行可 写映射,如果发生写操作,系统检查当前可用空间,能创建私有页面 时写操作按计划进行,可用空间不足以创建私有页面时,分发SIGBUS 或者 SIGSEGV 信号到做写操作的进程。MAP_NORESERVE 设置在fork() 后为子进程所继承,但在fork()的瞬间,对于父进程中已经存在的私 有页面(copy-on-write发生过了),在子进程中将为之保留空间,之 后子进程的映射行为和前面描述的相一致。 形参off应该对齐在页边界上,给sysconf(3C)传入形参_SC_PAGESIZE 或者_SC_PAGE_SIZE即可返回页大小。如果指定了 MAP_FIXED ,形参 addr也必须对齐在页边界上。系统自动完成整页映射,形参len将向 上舍入到页大小的整数倍(类似mprotect)。 系统总是用零填充最后一页越过文件尾的内存区域,即使修改了这个 区域,也永远不会将该区域的任何内容写入文件。引用这个区域的地 址导致分发 SIGBUS 或 SIGSEGV 信号。违背磁盘限额设置的时候, 也会分发 SIGBUS 信号。 被映射文件的 st_atime 将被更新成mmap(2)和相应munmap(2)之间的 某个时间。对映射内存区域第一次读/写时,如果这之前文件的 st_atime 未被标记/更新,则这次读/写操作导致文件的 st_atime 被标记/更新。 文件以 MAP_SHARED 方式可写映射,它的 st_ctime 和 st_mtime 将 被更新成内存写操作和任一相关进程对被写内存区域msync(3C)调用( 指定 MS_ASYNC 或者 MS_SYNC )之间的某个时间。如果未发生这样的 msync(3C)调用,st_ctime 和 st_mtime 将被更新成有效内存写操作 (指内存写操作最终影响了文件)之后的某个时间。 如果进程指定 MCL_FUTURE 标志调用过mlockall(3C),这之后mmap() 映射上来的页面均被锁定在内存中(不被交换到磁盘上去)。此时如果 没有足够内存满足锁定要求,mmap()调用失败,errno被设置成 EAGAIN。 返回值 成功则返回最终映射地址pa,失败时返回MAP_FAILED并设置errno 错误码 EACCES 1) 文件句柄fildes非可读打开,无论prot如何指定 2) 文件句柄fildes非可写打开,却准备以 MAP_SHARED 方式进行可写映射(PROT_WRITE) EAGAIN 映射上来的页面无法锁定在内存中 没有足够内存满足映射中所要求的保留空间 The file to be mapped is already locked using advisory or mandatory record locking. See fcntl(2). EBADF 文件句柄fildes尚未打开 EINVAL 形参addr(指定 MAP_FIXED)或者off未对齐在页边界 上(页大小从sysconf(3C)返回) 形参flags无效,既不是 MAP_PRIVATE 也不是 MAP_SHARED 形参len小于等于0 EMFILE 映射内存区域的数量超过某个实现相关的限制(基于 进程的或者基于整个系统的) ENODEV 文件句柄fildes描述的对象对于mmap()而言无意义, 比如终端。 ENOMEM 指定了MAP_FIXED,而[addr, addr + len)超出了进 程地址空间 未指定MAP_FIXED,但是没有足够空间完成映射 The composite size of len plus the lengths of all previous mmappings exceeds RLIMIT_VMEM (see getrlimit(2)). ENXIO 映射这个范围[off, off + len),对于对象(文件/ 设备)来说非法 EOVERFLOW The file is a regular file and the value of off plus len exceeds the offset maximum establish in the open file description asso- ciated with fildes. 用法 mmap()允许以内存操作方式访问对象,而不是read/write接口。 fildes = open(...) lseek( fildes, offset, whence ) read( fildes, buf, len ) /* use data in buf */ 下面是mmap()方式 fildes = open(...) address = mmap( ( caddr_t )0, len, ( PROT_READ | PROT_WRITE ), MAP_PRIVATE, fildes, offset ) /* use data at address */ The mmap() function has an explicit 64-bit equivalent. See interface64(5). 参看 close(2)、exec(2)、fcntl(2)、fork(2)、getrlimit(2)、mprotect(2) munmap(2)、shmat(2)、lockf(3C)、mlockall(3C)、msync(3C)、 plock(3C)、sysconf(3C)、interface64(5) -------------------------------------------------------------------------- 下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o copy_mmap copy_mmap.c */ #include #include #include /* for memcpy */ #include #include #include #include #include #include #define PERMS 0600 int main ( int argc, char * argv[] ) { int src, dst; void *sm, *dm; struct stat statbuf; if ( argc != 3 ) { fprintf( stderr, " Usage: %s \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 */ -------------------------------------------------------------------------- 对照这个使用标准文件I/O接口的实现 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -static -o copy_io copy_io.c */ #include #include #include #include #include #include #include #define BUFSIZE 8192 /* SPARC/Solaris 2.6上是一页的大小 */ #define PERMS 0600 int main ( int argc, char * argv[] ) { char iobuffer[ BUFSIZE ]; int src, dst, num; if ( argc != 3 ) { fprintf( stderr, " Usage: %s \n", argv[0] ); exit( EXIT_FAILURE ); } if ( ( src = open( argv[1], O_RDONLY ) ) < 0 ) { perror( "open source" ); exit( EXIT_FAILURE ); } if ( ( dst = open( argv[2], O_WRONLY | O_CREAT | O_TRUNC, PERMS ) ) < 0 ) { perror( "open target" ); exit( EXIT_FAILURE ); } while ( ( num = read( src, iobuffer, BUFSIZE ) ) > 0 ) { if ( write( dst, iobuffer, num ) != num ) { perror( "write" ); exit( EXIT_FAILURE ); } } close( src ); close( dst ); exit( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- mmap()好处是处理大文件时速度明显快于标准文件I/O,无论读写,都少了一次用户 空间与内核空间之间的复制过程。操作内存还便于设计、优化算法。 A: 小四 下面是x86/Linux RedHat 7.0中的mmap(2)手册页 -------------------------------------------------------------------------- MMAP(2) Linux程序员手册 MMAP(2) 名字 mmap、munmap - 将文件或设备映射到内存中 摘要 #include #include #ifdef _POSIX_MAPPED_FILES void * mmap ( void *start, size_t length, int prot, int flags, int fd, off_t offset ); int munmap ( void *start, size_t length ); #endif 描述 length指定映射长度,以字节为单位。offset指明文件或其他设备对象偏移,fd 对应文件句柄。形参start给内核一个建议的起始映射地址,并非强制指定。如 果start为0,表示内核自由选取最终映射地址。mmap()返回值指明了最终映射地 址。形参prot描述了期望的内存权限,必须和打开文件方式相匹配。 PROT_EXEC Pages may be executed. PROT_READ Pages may be read. PROT_WRITE Pages may be written. PROT_NONE Pages may not be accessed. 形参flags可取值如下 MAP_FIXED start此时为强制指定的映射地址,如果无法在指定地址上完成映射, mmap()失败返回。如果指定了 MAP_FIXED,start必须对齐在页边界上。 建议不要使用该设置。 MAP_SHARED 此种映射方式下,对内存区域的写操作等价于对文件的写操作,映射过 同一文件的其他进程会立即察觉到这次写操作。磁盘文件可能并非立即 更新,直到msync(2)或munmap(2)被调用。 MAP_PRIVATE 写时复制(copy-on-write)映射方式。此种映射方式下对内存的写操作 不会影响磁盘文件。 必须指定 MAP_SHARED 和 MAP_PRIVATE 中的一个,但不能同时指定。 上述三个flags在POSIX.1b(以前的 POSIX.4)中有描述。Linux自己还支持 MAP_DENYWRITE、MAP_EXECUTABLE、MAP_NORESERVE、MAP_LOCKED、 MAP_GROWSDOWN 以及 MAP_ANON(YMOUS) 形参offset通常应该位于页边界上,可由getpagesize(2)获取页尺寸。但是,这 个形参可以不位于页边界上。 译注:这里和SPARC/Solaris 2.6不同,Solaris要求offset必须对齐在页边 界上,否则返回EINVAL错误码。Linux下有所变化,注意这些差别。 munmap()拆除映射关系。无法引用被拆除映射关系的地址范围。进程终止时映射 自动拆除。关闭文件句柄fd并不会导致映射关系拆除。 返回值 mmap()成功返回一个指针,失败返回MAP_FAILED(-1),并设置errno。 munmap()成功返回0,失败返回-1,并设置errno(可能是EINVAL) 错误码 EBADF 形参fd是个无效文件句柄(同时未指定MAP_ANONYMOUS) EACCES 指定了MAP_PRIVATE,但是fd打开模式不包括读 指定了MAP_SHARED可写映射(PROT_WRITE),但是fd打开模式不是O_RDWR EINVAL 形参start、length或者offset有问题,比如太大范围、未对齐在页边界 上等等 ETXTBUSY 指定了MAP_DENYWRITE,但是fd打开模式包括写 EAGAIN 文件被锁定,或者太多内存被锁定 ENOMEM 无足够内存可用 使用映射可能涉及到如下信号 SIGSEGV 试图对只读映射区域进行写操作 SIGBUS 试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域, 或者以前有文件内容对应,现在为另一进程截断过的内存区域。 遵从 SVr4、POSIX.1b(以前的POSIX.4)、4.4BSD Svr4标准中包括额外的错误码 ENXIO 和 ENODEV 参看 getpagesize(2)、msync(2)、shm_open(2) -------------------------------------------------------------------------- 下面的测试程序在x86/Linux RedHat 7.0上调试通过 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o procmem_mmap_2 procmem_mmap_2.c */ #include #include #include /* for memcpy */ #include #include #include #include #include #include #include #include /* for PAGESIZE */ #ifndef PAGESIZE #define PAGESIZE 4096 #endif 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 */ int main ( int argc, char * argv[] ) { int fd; void *mem; char buf[ PAGESIZE ]; off_t offset; if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 ) { perror( "open /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "main = %#x\n", ( unsigned int )&main ); outputBinary ( ( const unsigned char * )&main, 32 ); offset = ( off_t )&main + 1; if ( lseek( fd, offset, SEEK_SET ) < 0 ) { perror( "lseek /proc/self/mem" ); exit( EXIT_FAILURE ); } if ( read( fd, buf, PAGESIZE ) != PAGESIZE ) { perror( "read /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "offset = %#x\n", ( unsigned int )offset ); outputBinary ( ( const unsigned char * )buf, 32 ); /* * 读的时候指定 MAP_PRIVATE 即可 */ mem = mmap( 0, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, offset ); if ( MAP_FAILED == mem ) { perror( "mmap /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "mem = %#x\n", ( unsigned int )mem ); outputBinary ( ( const unsigned char * )mem, 32 ); offset = ( off_t )( ( ( unsigned int )&main & ~( PAGESIZE - 1 ) ) + PAGESIZE ); if ( lseek( fd, offset, SEEK_SET ) < 0 ) { perror( "lseek /proc/self/mem" ); exit( EXIT_FAILURE ); } if ( read( fd, buf, PAGESIZE ) != PAGESIZE ) { perror( "read /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "offset = %#x\n", ( unsigned int )offset ); outputBinary ( ( const unsigned char * )buf, 32 ); if ( munmap( mem, PAGESIZE ) < 0 ) { perror( "munmap /proc/self/mem" ); exit( EXIT_FAILURE ); } close( fd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- $ ./procmem_mmap_2 main = 0x804886c byteArray [ 32 bytes ] ----> 00000000 55 89 E5 57 56 53 81 EC-24 10 00 00 6A 00 68 81 00000010 8B 04 08 E8 A4 FC FF FF-89 C3 83 C4 10 85 DB 79 offset = 0x804886d byteArray [ 32 bytes ] ----> 00000000 89 E5 57 56 53 81 EC 24-10 00 00 6A 00 68 81 8B 00000010 04 08 E8 A4 FC FF FF 89-C3 83 C4 10 85 DB 79 1B mem = 0x40017000 byteArray [ 32 bytes ] ----> 00000000 7F 45 4C 46 01 01 01 00-00 00 00 00 00 00 00 00 .ELF............ 00000010 02 00 03 00 01 00 00 00-60 85 04 08 34 00 00 00 ........`?.4... offset = 0x8049000 byteArray [ 32 bytes ] ----> 00000000 7F 45 4C 46 01 01 01 00-00 00 00 00 00 00 00 00 .ELF............ 00000010 02 00 03 00 01 00 00 00-60 85 04 08 34 00 00 00 ........`?.4... 这个输出说明了几个问题。文件I/O操作/proc/self/mem不存在页边界对齐的问题。 至少Linux的mmap()的最后一个形参offset并未强制要求页边界对齐,如果提供的值 未对齐,系统自动向上舍入到页边界上。 D: 小四 下面是一个利用mmap()完成内存拷贝的演示程序,在RedHat 7.0下调试通过 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o procmem_mmap_3 procmem_mmap_3.c */ #include #include #include /* for memcpy */ #include #include #include #include #include #include #include #include /* for PAGESIZE */ #ifndef PAGESIZE #define PAGESIZE 4096 #endif 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 */ int main ( int argc, char * argv[] ) { int fd; void *oldsrc, *olddst, *src, *dst, *mem; if ( NULL == ( oldsrc = malloc( PAGESIZE + PAGESIZE ) ) ) { perror( "malloc oldsrc" ); exit( EXIT_FAILURE ); } src = ( void * )( ( ( unsigned int )oldsrc & ~( PAGESIZE - 1 ) ) + PAGESIZE ); if ( NULL == ( olddst = malloc( PAGESIZE + PAGESIZE ) ) ) { perror( "malloc olddst" ); exit( EXIT_FAILURE ); } dst = ( void * )( ( ( unsigned int )olddst & ~( PAGESIZE - 1 ) ) + PAGESIZE ); fprintf( stderr, "oldsrc = %#x\n", ( unsigned int )oldsrc ); fprintf( stderr, "src = %#x\n", ( unsigned int )src ); strcpy( ( char * )src, "NsFocus Security Team." ); outputBinary ( ( const unsigned char * )src, 32 ); fprintf( stderr, "olddst = %#x\n", ( unsigned int )olddst ); fprintf( stderr, "dst = %#x\n", ( unsigned int )dst ); outputBinary ( ( const unsigned char * )dst, 32 ); /* 后面没有写操作,也不做可写映射,所以这里可以只读打开 */ if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 ) { perror( "open /proc/self/mem" ); exit( EXIT_FAILURE ); } /* * 为了完成到指定地址的内存拷贝,需要指定 MAP_FIXED * 我们必须自己保证相关形参不违反页边界对齐要求 */ mem = mmap( dst, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE | MAP_FIXED, fd, ( off_t )src ); if ( MAP_FAILED == mem ) { perror( "mmap /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "mem = %#x\n", ( unsigned int )mem ); outputBinary ( ( const unsigned char * )dst, 32 ); if ( munmap( mem, PAGESIZE ) < 0 ) { perror( "munmap /proc/self/mem" ); exit( EXIT_FAILURE ); } /* * 如果这里多这么一行代码 * outputBinary ( ( const unsigned char * )dst, 32 ); * Segmentation fault (core dumped) * * gdb跟踪显示这里引发了SIGSEGV信号 * * 那么这种情况下发生了什么?一块内存不见了,后续的free( olddst )为什 * 么可以正常执行下去而不导致Segmentation fault,会出现内存泄露吗? * munmap()是否可以操作非mmap()返回的指针?它和free()什么关系? * 利用mmap()完成内存拷贝,意义何在,速度吗? */ close( fd ); free( olddst ); free( oldsrc ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- $ ./procmem_mmap_3 oldsrc = 0x8049d50 src = 0x804a000 byteArray [ 32 bytes ] ----> 00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security 00000010 20 54 65 61 6D 2E 00 00-00 00 00 00 00 00 00 00 Team........... olddst = 0x804bd58 dst = 0x804c000 byteArray [ 32 bytes ] ----> 00000000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00000010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ mem = 0x804c000 byteArray [ 32 bytes ] ----> 00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security 00000010 20 54 65 61 6D 2E 00 00-00 00 00 00 00 00 00 00 Team........... $ 这个演示程序说明了另外几个问题。malloc()分配得到的地址不见得对齐在页边界上, 其他问题请仔细参看上面的注释部分。个人觉得mmap()还是很容易掌握的,只要按照 它的本质规律做。 要照注释里的情况,这种指定了MAP_FIXED的映射正常如何使用呢。如果上面的dst不 是malloc()获取的地址空间,而是自动变量(stack区中),munmap()拆除映射后访问 dst是否还会导致Segmentation fault (core dumped)?free()的内核实现很奇怪, 这里居然能正常释放olddst指针。 A: scz Linux下的/proc/self/mem对应进程自身的内存映像。参看proc(5)手册页。 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o procmem_mmap procmem_mmap.c */ #include #include #include /* for memcpy */ #include #include #include #include #include #include #include #include /* for PAGESIZE */ #ifndef PAGESIZE #define PAGESIZE 4096 #endif 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 */ int main ( int argc, char * argv[] ) { int fd; void *mem; char buf[ PAGESIZE ]; off_t offset; unsigned int main_offset; if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 ) { perror( "open /proc/self/mem" ); exit( EXIT_FAILURE ); } offset = ( off_t )( ( unsigned int )&main & ~( PAGESIZE - 1 ) ); main_offset = ( unsigned int )&main & ( PAGESIZE - 1 ); fprintf( stderr, "main = %#x\n", ( unsigned int )&main ); outputBinary ( ( const unsigned char * )&main, 32 ); fprintf( stderr, "offset = %#x\n", ( unsigned int )offset ); fprintf( stderr, "main_offset = %#x\n", main_offset ); if ( lseek( fd, offset, SEEK_SET ) < 0 ) { perror( "lseek /proc/self/mem" ); exit( EXIT_FAILURE ); } if ( read( fd, buf, PAGESIZE ) != PAGESIZE ) { perror( "read /proc/self/mem" ); exit( EXIT_FAILURE ); } outputBinary ( ( const unsigned char * )buf + main_offset, 32 ); /* 读的时候指定 MAP_PRIVATE 即可 */ mem = mmap( 0, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, offset ); if ( MAP_FAILED == mem ) { perror( "mmap /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "mem = %#x\n", ( unsigned int )mem ); outputBinary ( ( const unsigned char * )mem + main_offset, 32 ); if ( munmap( mem, PAGESIZE ) < 0 ) { perror( "munmap /proc/self/mem" ); exit( EXIT_FAILURE ); } close( fd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 程序演示了对/proc/self/mem的两种操作方式,标准文件I/O和mmap()。观察三个 outputBinary()的输出,三者应该是一致的。在APUE的APUE版有朋友提出文 件I/O失败,那是因为他程序中offset为0,显然用户空间进程无权读取自身进程空间 中0地址处的数据,即使当前是root身份也不行,毕竟有用户空间和内核空间的区别。 mmap()失败原因类似。而上面这个演示程序使用了main()函数所在页,这个地址上无 论标准文件I/O还是mmap()都会成功。至少RedHat 6.2已经允许对/proc/self/mem做 mmap()操作,参看proc(5)手册页。说什么RedHat不支持对/proc/self/mem做mmap() 操作,是错误的结论。 /proc/self/mem和/dev/kmem不同。root用户打开/dev/kmem就可以在用户空间访问到 内核空间的数据,包括偏移0处的数据,系统提供了这样的支持。 普通用户可以执行procmem_mmap,不要求超级用户权限。 objdump -j .text -S procmem_mmap | more /main <-- 搜索main函数 80487a4: 55 pushl %ebp 80487a5: 89 e5 movl %esp,%ebp 80487a7: 81 ec 08 10 00 00 subl $0x1008,%esp 80487ad: 57 pushl %edi 80487ae: 56 pushl %esi 80487af: 53 pushl %ebx 80487b0: 6a 00 pushl $0x0 80487b2: 68 01 8a 04 08 pushl $0x8048a01 80487b7: e8 f0 fc ff ff call 80484ac <_init+0xd0> 80487bc: 89 85 fc ef ff ff movl %eax,0xffffeffc(%ebp) 80487c2: 83 c4 08 addl $0x8,%esp $ ./procmem_mmap_3 oldsrc = 0x8049d50 src = 0x804a000 byteArray [ 32 bytes ] ----> 00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security 00000010 20 54 65 61 6D 2E 00 00-00 00 00 00 00 00 00 00 Team........... olddst = 0x804bd58 dst = 0x804c000 byteArray [ 32 bytes ] ----> 00000000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 00000010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ mem = 0x804c000 byteArray [ 32 bytes ] ----> 00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security 00000010 20 54 65 61 6D 2E 00 00-00 00 00 00 00 00 00 00 Team........... 可以用objdump -p <...>查看程序加载地址,加载地址之后的都可以正常文件I/O读 取,加载地址之前的无法正常文件I/O读取。这个说法欠妥,应该是cat /proc//maps 查看究竟哪些内存区域建立了映射关系,然后可以正常文件I/O读取。 D: scz 如果这里以 MAP_SHARED 方式可写映射/proc/self/mem,main()所在代码段是否可写 了?换句话说,是否mmap()也可以达到mprotect()的效果呢。 此外,只要有足够权限,通过/proc//mem就可以访问其他进程的地址空间了, 这点可以确认吗? 下面的测试程序在RedHat Linux 6.2上调试通过 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o procmem_mmap_1 procmem_mmap_1.c */ #include #include #include /* for memcpy */ #include #include #include #include #include #include #include #include /* for PAGESIZE */ #ifndef PAGESIZE #define PAGESIZE 4096 #endif 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 */ int main ( int argc, char * argv[] ) { int fd; void *mem; char buf[ PAGESIZE ]; off_t offset; unsigned int main_offset; /* * 读写打开,准备可写映射 */ if ( ( fd = open( "/proc/self/mem", O_RDWR ) ) < 0 ) { perror( "open /proc/self/mem" ); exit( EXIT_FAILURE ); } offset = ( off_t )( ( unsigned int )&main & ~( PAGESIZE - 1 ) ); main_offset = ( unsigned int )&main & ( PAGESIZE - 1 ); fprintf( stderr, "main = %#x\n", ( unsigned int )&main ); /* * 原来的数据 */ outputBinary ( ( const unsigned char * )&main, 32 ); /* * MAP_SHARED 方式的可写映射,否则/proc/self/mem不会被修改 */ mem = mmap( 0, PAGESIZE, PROT_WRITE, MAP_SHARED, fd, offset ); if ( MAP_FAILED == mem ) { perror( "mmap /proc/self/mem" ); exit( EXIT_FAILURE ); } fprintf( stderr, "mem = %#x\n", ( unsigned int )mem ); outputBinary ( ( const unsigned char * )mem + main_offset, 32 ); /* * 这里修改了代码段 */ strcpy( ( char * )mem + main_offset, "NsFocus Security Team." ); fprintf( stderr, "offset = %#x\n", ( unsigned int )offset ); fprintf( stderr, "main_offset = %#x\n", main_offset ); if ( lseek( fd, offset, SEEK_SET ) < 0 ) { perror( "lseek /proc/self/mem" ); exit( EXIT_FAILURE ); } if ( read( fd, buf, PAGESIZE ) != PAGESIZE ) { perror( "read /proc/self/mem" ); exit( EXIT_FAILURE ); } outputBinary ( ( const unsigned char * )buf + main_offset, 32 ); fprintf( stderr, "main = %#x\n", ( unsigned int )&main ); /* * 现在的数据 */ outputBinary ( ( const unsigned char * )&main, 32 ); if ( munmap( mem, PAGESIZE ) < 0 ) { perror( "munmap /proc/self/mem" ); exit( EXIT_FAILURE ); } close( fd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- $ ./procmem_mmap_1 main = 0x8048814 byteArray [ 32 bytes ] ----> 00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 02 68 A1 00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 mem = 0x40015000 byteArray [ 32 bytes ] ----> 00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 02 68 A1 00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 offset = 0x8048000 main_offset = 0x814 byteArray [ 32 bytes ] ----> 00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security 00000010 20 54 65 61 6D 2E 00 FF-89 85 FC EF FF FF 83 C4 Team.. main = 0x8048814 byteArray [ 32 bytes ] ----> 00000000 4E 73 46 6F 63 75 73 20-53 65 63 75 72 69 74 79 NsFocus Security 00000010 20 54 65 61 6D 2E 00 FF-89 85 FC EF FF FF 83 C4 Team.. 显然代码段经过/proc/self/mem可写映射后已经可写,无须mprotect()介入。 Linux下有/proc//mem可利用,Solaris和*BSD呢,有无类似的伪文件系统接口 提供?如果有可以做很多有意义的实验。 D: scz Solaris 2.6下参看getpagesize(3C)手册页,关于如何获取页大小,一般是8192。 Linux下参看getpagesize(2)手册页,一般是4096。 3.4 getrusage如何用 A: 小四 在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 为什么要学习使用setitimer(2),因为alarm(3)属于被淘汰的定时器技术。 A: 小四 man -S 2 setitimer,下面是x86/FreeBSD 4.3-RELEASE的getitimer(2)手册页 -------------------------------------------------------------------------- GETITIMER(2) FreeBSD系统调用手册 GETITIMER(2) 名字 getitimer, setitimer - 获取/设置interval timer(怎么翻译这个) 库 标准C库(libc, -lc) 摘要 #include #define ITIMER_REAL 0 #define ITIMER_VIRTUAL 1 #define ITIMER_PROF 2 int getitimer ( int which, struct itimerval *value ); int setitimer ( int which, const struct itimerval *value, struct itimerval *ovalue ); 描述 系统为每个进程提供了3种不同类型的interval timer,参看 getitimer()返回which对应的value,setitimer()设置which,如果ovalue不为 NULL,则同时返回旧值。which取值0、1、2。 itimerval结构定义如下 struct itimerval { struct timeval it_interval; /* timer interval */ struct timeval it_value; /* current value */ }; 如果it_value非零,指明定时器当前超时时间。如果it_interval非零,指明定 时器超时后用该值重新设置it_value。如果it_value为零,将disable定时器, 此时与it_interval无关。如果it_interval为零,导致定时器超时后被disable 掉。 比系统时钟频率更小的时钟设置将被向上舍入到系统时钟频率,典型是10毫秒 ITIMER_REAL 定时器按照真实时钟递减,超时发生后分发SIGALRM信号 ITIMER_VIRTUAL 定时器按照进程虚拟时钟递减,仅当进程执行中运行。超时发 生后分发SIGVTALRM信号 ITIMER_PROF 定时器在进程虚拟时钟中递减,当系统为进程利益而运行时也递减 该时钟。解释器使用它统计被解释程序的执行效率。超时发生后,分发SIGPROF 信号。该信号可能中断进程中正在进行的系统调用,使用这个定时器的程序必须 考虑到重启被中断的系统调用。 it_interval 和 it_value 所允许的最大秒数是100000000 注意 在中定义了几个宏操作timeval结构 #define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0 #define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) #define timercmp(tvp, uvp, cmp) (((tvp)->tv_sec == (uvp)->tv_sec) ? \ ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ ((tvp)->tv_sec cmp (uvp)->tv_sec)) timerclear将时钟清零,timerisset测试时钟是否为零,timercmp比较两个时钟。 返回值 0 成功 -1 失败,设置全局变量errno 错误码 [EFAULT] 第二形参value指定了一个无效地址 [EINVAL] 第二形参value对应的时钟太大了,以致无法处理 参看 gettimeofday(2), select(2), sigvec(2), clocks(7) 历史 getitimer()从4.2 BSD开始出现 -------------------------------------------------------------------------- 下面是个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 #include #include #include /************************************************************************ * * * 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 */ /************************************************************************/ -------------------------------------------------------------------------- 下面是个x86/OpenBSD 3.0下的例子 -------------------------------------------------------------------------- /* * File : timer_sample_1.c * Complie : gcc -Wall -pipe -O3 -o timer_sample_1 timer_sample_1.c * Platform : x86/OpenBSD 3.0 * Date : 2002-10-08 15:05 */ /************************************************************************ * * * Head File * * * ************************************************************************/ #include #include #include #include #include /************************************************************************ * * * 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 void outputBinary ( const unsigned char *byteArray, const size_t byteArrayLen ); 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 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 */ 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[] ) { struct itimerval value; int which; init_signal(); init_timer(); while ( 1 ) { /* * 形成阻塞,降低CPU占用率 */ getchar(); /* * SIGALRM信号句柄做了特殊处理,并不重启被其中断的系统调用。下面的 * 代码无需在标准输入上提交数据就能够得到执行。getchar()形成的阻塞 * 是被SIGALRM信号打破的。 */ for ( which = ITIMER_REAL; which <= ITIMER_PROF; which++ ) { memset( &value, 0, sizeof( value ) ); if ( getitimer( which, &value ) < 0 ) { perror( "getitimer error" ); return( EXIT_FAILURE ); } outputBinary( ( const unsigned char * )&value, sizeof( value ) ); } } return( EXIT_SUCCESS ); } /* end of main */ /************************************************************************/ -------------------------------------------------------------------------- D: scz 讨论一个问题。getchar()的作用是降低CPU占用率,可用top命令查看。 timer_sample.c中换用ITIMER_PROF/SIGPROF后,你会发现上述程序无输出,我据此 认为getchar()形成的阻塞不计算在进程虚拟时钟中,也不认为系统正在为进程利益 而运行。 如果进一步将getchar()去掉,直接一个while()无限循环,即使换用 ITIMER_PROF/SIGPROF,程序还是有输出。不过top命令查看的结果让你吐血,CPU几 乎无空闲。 D: scz setitimer( ITIMER_REAL, &value, NULL )导致分发SIGALRM信号,如果同时使用 alarm(),势毕造成冲突。此外注意sleep()、pause()等函数带来的冲突。 4. 系统资源相关问题 4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况 Q: Solaris下如何编程获知CPU占用率和内存占用信息呢,可移植吗? Q: 我想写个程序遍历当前运行中的活动进程,Solaris提供相应系统调用了吗 A: Nicholas Dronen 不可移植。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 man -s 3C getrusage D: 小四 在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 对于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 #include #include #include #include #include #include #include #include #include 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 psrinfo -v psrinfo | grep on-line | wc -l 简单给出CPU数目 A: Philly Bob 在OK提示符下输入"module-info"获知CPU数目以及频率 A: scz # /usr/platform/`uname -i`/sbin/prtdiag -v # /usr/platform/`uname -m`/sbin/prtdiag -v # /usr/bin/netstat -k cpu_info0 A: Tony Walton 如果你装了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 几个现成命令 /usr/platform/`uname -m`/sbin/prtdiag -v | grep Memory prtconf -v | grep Memory 如果装了GNU top,也可以直接用top命令看到。 D: scz 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 -------------------------------------------------------------------------- /* * Program to determine the size installed physical memory on Suns. * * Casper Dik. */ #define MEGABYTE 0x00100000 #define MAXMEM 0x7ff00000 #define THEMEM "/dev/mem" #include #include #include #include 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 ); } -------------------------------------------------------------------------- 在SPARC/Solaris 7 64-bit kernel mode下编译32-bit应用程序 $ gcc -Wall -pipe -O3 -o memcount memcount.c $ /usr/ccs/bin/strip memcount 在SPARC/Solaris 7 64-bit kernel mode下编译64-bit应用程序 $ /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o memcount memcount.c $ /usr/ccs/bin/strip memcount 由于需要读访问/dev/mem,普通用户无法使用该程序。 5. 块设备相关问题 5.0 Solaris/FreeBSD/Linux中如何mount ISO文件 A: SMTH/Unix版 2003-04-11 xfgavin@SMTH 在Linux中 mount -o loop your.iso bcl@SMTH 在FreeBSD中 vnconfig vn0 your.iso mount_cd9660 /dev/vn0c ... ... umount 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 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/glone/employees/joerg.schilling/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 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 $ 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 检查是否打了如下Solaris 7内核补丁106541-13和 -14。这两个补丁有问题。解 决办法是禁止vold,手工mount光驱。 A: rschicht@my-deja.com 试试volrmmount -d命令。用patchadd -p检查是否安装了补丁106541-14,访问如 下链接 http://sunsolve.Sun.COM/pub-cgi/show.pl?target=patches/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 下面的操作或许有所帮助 1) 终止卷管理器 # /etc/init.d/volmgt stop 2) unmount光驱,手动eject 3) 重启卷管理器 # /etc/init.d/volmgt start D: plane@SMTH 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 正确的用法就在错误提示信息里,你应该使用另外的超级块进行恢复工作,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 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 启动时按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 使用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 如果希望对第二块物理硬盘的分区与第一块物理硬盘一样,考虑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@SMTH 在安装盘里有一个tools目录,进去后有一个命令叫做installboot A: melonm@SMTH 比如 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 查看/etc/minor_perm文件, # grep -s null /etc/minor_perm mm:null 0620 root tty 修改该文件中的这一行成"mm:null 0666 root sys"即可。 5.9 如何读取Solaris disk label信息 A: Sun Microsystems 1998-03-25 对于SPARC和X86架构来说,读取disk label信息的方式略有区别,主要原因在于分区 信息组织方式不同、设备名不同。 -------------------------------------------------------------------------- /***************************************************************/ /* */ /* gcc -DSPARC_ARC -Wall -pipe -O3 -o rdlabel rdlabel.c */ /* */ /* rdlabel -- Read the disk label */ /* */ /* Usage: */ /* rdlabel */ /* */ /* Notes: on SPARC arch devices are /dev/rdsk/cXtYdZsS */ /* OTHER arch devices are /dev/rdsk/cXtYdZpN */ /* */ /* X = controller, */ /* Y = target, */ /* Z = disk #, */ /* N = fdisk partition */ /* S = 0 or 2 */ /* */ /***************************************************************/ #include #include #include #include #include #include #include #ifdef SPARC_ARC #define ZEXAMPLE "Example: rdlabel /dev/rdsk/c0t0d0s0\n" #else #define ZEXAMPLE "Example: rdlabel /dev/rdsk/c0t0d0p1\n" #endif void usage ( void ) { fprintf( stderr, "Usage : rdlabel \n" ); fprintf( stderr, "Warning: You must be root to run rdlabel!\n" ); fprintf( stderr, ZEXAMPLE ); return; } int main ( int argc, char * argv[] ) { #ifdef SPARC_ARC int label_loc = ( 512 * DK_LABEL_LOC ); #else int label_loc = 0; #endif int disk_fd; struct dk_label label; char buffer[80]; if ( argc < 2 ) { fprintf( stderr, "Error: Argument expected!\n" ); usage(); exit( EXIT_FAILURE ); } sprintf( buffer, "%s", argv[1] ); disk_fd = open( buffer, O_RDONLY ); if ( disk_fd < 0 ) { perror( "Uable to open device " ); exit( EXIT_FAILURE ); } else { lseek( disk_fd, label_loc, SEEK_SET ); ( void )read( disk_fd, ( char * )&label, sizeof( label ) ); ( void )close( disk_fd ); } ( void )printf( "Read label for device %s returned the following:\n", buffer ); ( void )printf( "<%s>\n", label.dkl_asciilabel ); return( EXIT_SUCCESS ); } -------------------------------------------------------------------------- 由于访问了原始设备,必须以root身份执行rdlabel。下面分别是X86和SPARC架构上 的输出举例: X86: SPARC: D: scz 下面是我实验机(SPARC/Solaris 7)上的输出 # rdlabel /dev/rdsk/c0t0d0s0 Read label for device /dev/rdsk/c0t0d0s0 returned the following: # prtvtoc /dev/rdsk/c0t0d0s0 <-- 执行/usr/sbin/prtvtoc命令 * /dev/rdsk/c0t0d0s0 partition map * * Dimensions: * 512 bytes/sector <-- 每个扇区512字节 * 63 sectors/track <-- 每个磁道63个扇区 * 15 tracks/cylinder <-- 每个柱面15个磁道 * 945 sectors/cylinder <-- 每个柱面945个扇区 * 8894 cylinders <-- 总共8894个柱面 * 8892 accessible cylinders <-- 8892个可用柱面 * * Flags: * 1: unmountable * 10: read-only * * Unallocated space: * First Sector Last * Sector Count Sector * 8401050 1890 8402939 * * First Sector Last * Partition Tag Flags Sector Count Sector Mount Directory 0 2 00 0 2457945 2457944 / 1 3 01 2457945 302400 2760344 2 5 00 0 8402940 8402939 6 4 00 2760345 3072195 5832539 /usr 7 8 00 5832540 2568510 8401049 /export/home 对比这些输出,参看prtvtoc(1M)手册页了解更多细节。 5.10 如何自己制作Solaris启动软盘 Q: 我知道可以去 http://soldc.sun.com/support/drivers/dca_diskettes/ 下载启动软盘的映象文件,可我还想知道它最初是如何制作出来的 A: 小四 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 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/ D: 小四 注意,Solaris的slice概念和FreeBSD的slice概念不同,FreeBSD的slice概念就是MS 的partition概念,而Solaris的slice概念类似于MS扩展分区上的逻辑驱动器概念。 6. /etc/system可调资源限制 6.1 Solaris下如何限制每个用户可拥有的最大进程数 A: Casper Dik 在/etc/system设置 set maxuprc = 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= 执行/usr/sbin/reboot -- -r,或者Stop-A,执行boot -r b. 对于Solaris 8,支持的伪终端数目根据需要动态改变,系统依然有一个内部限制, 但是这个值非常大。如果"pt_cnt"变量小于这个内部限制,将被忽略。一般情况 下,不再需要指定"pt_cnt"变量。但还是有某些罕见的情形,需要设置"pt_cnt" 变量大于内部限制。 6.3 如何增加每个进程可打开文件句柄数 A: Casper H.S. Dik 从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 检查当前设置 # 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 > /proc/sys/fs/file-max 6.5 做了setuid()这类调用的程序如何产生core dump Q: 做了setuid()这类调用的程序不会产生core文件,可我需要调试这个程序。 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o suidtest suidtest.c */ #include #include #include #include #include #include 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 如果想立即生效(不重启系统),可以利用adb命令 # nm -nx /dev/ksyms | grep allow_setid_core [8347] |0x0000104601e4|0x000000000004|OBJT |GLOB |0 |ABS |allow_setid_core # echo "allow_setid_core /X" | adb -k /dev/ksyms /dev/mem # echo "allow_setid_core /W 1" | adb -kw /dev/ksyms /dev/mem 对于Solaris 2.6,需要先打补丁105181-22或更高版本,才能使用上述技术。对于 7及其更高版本的Solaris操作系统,不需要任何补丁。 D: scz 见鬼,在SPARC/Solaris 7上测试了上述技术,无效! core文件可能包含系统敏感信息,比如shadow片段。而且core文件通常巨大,容易导 致拒绝服务攻击。如果并不在服务器上调试程序或者根本不知道什么是core文件,考 虑在/etc/system文件中增加 set sys:coredumpsize = 0 参看limit(1)手册页了解更多细节。可我怎么利用adb获取这个内核参数的当前值呢? D: scz 参看<<18.6 如何安装补丁>>,给SPARC/Solaris 2.6打补丁105181-28,并在 /etc/system中设置 set allow_setid_core = 1 重启系统后 # /usr/ccs/bin/nm -nx /dev/ksyms | grep "|allow_setid_core$" [11740] |0x10428848|0x00000004|OBJT |GLOB |0 |ABS |allow_setid_core # echo "allow_setid_core /X" | adb -k /dev/ksyms /dev/mem physmem 3d1f allow_setid_core: allow_setid_core: 1 # 注意,在补丁105181-28未打之前,上述两个操作无法完成,没有内核符号 "allow_setid_core"。 # ./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 段错误 (core dumped) <-- 注意这里,core dump产生了 # ls -l core -rw------- 1 root other 78992 6月 30 01:06 core # rm core # echo "allow_setid_core /W 0" | adb -kw /dev/ksyms /dev/mem physmem 3d1f allow_setid_core: 0x1 = 0x0 # ./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 段错误 <-- 注意这里,没有产生core dump # ls -l core core: 无此文件或目录 # faint,为什么对于SPARC/Solaris 2.6,打了补丁之后,该技术居然有效。 A: 小四 2001-07-30 20:14 对于SPARC/Solaris 7,修改/etc/system set allow_setid_core = 1 结果在boot的时候看到提示信息说这个内核参数已经废弃(obsolte),推荐使用 coreadm(1M),下面是其SPARC/Solaris 7下的man手册页 -------------------------------------------------------------------------- 维护命令 coreadm(1M) 名字 coreadm - core文件管理工具 摘要 coreadm [ -g pattern ] [ -i pattern ] [ -d option ... ] [ -e option ... ] coreadm [ -p pattern ] [ pid ... ] coreadm -u 描述 coreadm命令用于指定进程异常结束产生core文件时用什么名字以及 存放在哪里,参看core(4)手册页。 摘要中第一种命令方式只能为超级用户所用,配置结果影响整个系统 范围。包括全局core文件名模板以及针对init(1M)进程的per-process core文件名模板。所有这些设置在影响当前运行系统的同时,将存入 coreadm的配置文件/etc/coreadm.conf,系统重启时会读取这个配置 文件。参看init(1M)手册页。 普通用户可以执行第二种命令方式,指定per-process core文件名模 板,系统重启后失效。 超级用户才可以执行第三种命令方式,根据配置文件/etc/coreadm.conf 的内容更新系统范围内的设置。启动脚本/etc/init.d/coreadm会执 行该命令。 core文件名模板中可以使用如下变量 %p 进程PID %u EUID %g EGID %f 可执行文件名 %n 系统结点名(uname -n) %m 机器名(uname -m,比如sun4u) %t 以十进制表示的time(2)返回值,从UTC 1970-01-01 00:00:00到现 在的秒数 %% %号自身 例如 /var/core/core.%f.%p,程序foo执行后PID为1234,如果产生core文 件,将是/var/core/core.foo.1234 不带任何参数执行coreadm命令,将报告当前系统设置 $ coreadm global core file pattern: /var/core/core.%f.%p init core file pattern: core global core dumps: enabled per-process core dumps: enabled global setid core dumps: enabled per-process setid core dumps: disabled global core dump logging: disabled $ 带着一系列进程号执行coreadm命令,将顺序报告每个进程的 per-process core文件名模板,比如 $ coreadm 278 5678 278: core.%f.%p 5678: /home/george/cores/%f.%p.%t $ 只有进程属主或者超级用户可以这种方式使用coreadm命令 一个进程core dump时,操作系统可能产生两个core文件,全局core 文件和per-process core文件。 全局core文件的创建模式600,属主是超级用户,非超级用户无法检 查该文件。 per-process core文件创建模式600,属主与进程的credentials相符 合。进程属主可以检查该文件。 缺省情况下suid/sgid程序不允许core dump,避免泄露敏感信息。这 里需要这样理解, 进程最后一次exec(2)之后,假设某条代码即将导致core dump,而到 达该代码之前曾经拥有过超级用户权限,比如setuid-to-root、 setgid-to-root,甚至包括直接以root身份启动进程,中途调用 setuid(2)放弃特权这种情况,为了避免泄露敏感信息,系统缺省将 阻止产生core文件。利用coreadm -e可以允许此时产生core文件,模 式600,属主为超级用户。 选项 -g pattern 设置全局core文件名模板,模板必须以/开始,可以使用% 变量 只有超级用户可以使用该选项 -i pattern 针对init(1M)设置per-process core文件名模板,类似于 coreadm -p pattern 1,区别在于前者的设置即使重启动 也有效,因为会存入/etc/coreadm.conf中。 只有超级用户可以使用该选项 -e option... 打开指定的设置 global 允许core dump,并使用全局core文件名模板 process 允许core dump,并使用per-process core文 件名模板 global-setid 允许suid/sgid程序core dump,并使用全局 core文件名模板 proc-setid 允许suid/sgid程序core dump,并使用 per-process core文件名模板 log 试图产生一个全局core文件时,产生一个 syslog(3)消息 命令行上可以多次指定-e 和 -d选项,只有超级用户可以使用这两个 选项。 -d option... 关闭指定的设置。 -p pattern 设置per-process core文件名模板,可以使用%变量,不 必以/开始。如果未以/开始,以当前目录为根产生core文 件 普通用户使用-p选项时只能针对自己拥有的进程,超级用 户可以指定任意进程。per-process core文件名模板将被 受影响进程的子进程继承。参看fork(2)手册页。 -u 根据配置文件/etc/coreadm.conf更新系统范围的设置。 如果配置文件不存在,或者包含无效值,将使用缺省值。 虽然不建议手工修改/etc/coreadm.conf文件,但还是可 以这么做,然后执行coreadm -u进行同步。 只有超级用户可以使用该选项。 退出码 0 成功完成 1 读取/修改系统配置文件时发生致命错误 2 无效命令行选项 例子 $ coreadm -p core.$f.%p $$ $$ 是当前运行中shell的PID。per-process core文件名模板将被所 有子进程继承。可以在$HOME/.profile或$HOME/.login中增加该命令, 则登录会话中的所有进程都受此设置影响。 $ coreadm -p $HOME/corefiles/%n.%f.%p $$ 相关文件 /etc/init.d/coreadm /etc/coreadm.conf 参看 gcore(1)、init(1M)、exec(2)、fork(2)、setuid(2)、time(2) syslog(3)、core(4)、attributes(5) -------------------------------------------------------------------------- 现在我们来看这样一个/etc/coreadm.conf文件 -------------------------------------------------------------------------- # # coreadm.conf # # Parameters for system core file configuration. # Do NOT edit this file by hand -- use coreadm(1) instead. # COREADM_GLOB_PATTERN= COREADM_INIT_PATTERN=core COREADM_GLOB_ENABLED=no COREADM_PROC_ENABLED=yes COREADM_GLOB_SETID_ENABLED=no COREADM_PROC_SETID_ENABLED=no COREADM_GLOB_LOG_ENABLED=no -------------------------------------------------------------------------- 回到我们的问题中来,对于SPARC/Solaris 7来说,为了方便调试,执行 coreadm -e proc-setid命令即可。 6.6 消息队列调整 Q: 在/etc/system中如何调整消息队列 A: 消息队列统一使用 msgsys:msginfo_ 前缀。你可以用sysdef获取一些缺省值,还可 以参看/usr/include/sys/msg.h头文件了解更多信息。此外不要忘记<> msgsys:msginfo_msgmap default 100 max 2147483647 100 msgsys:msginfo_msgmax default 2048 max 2147483647 8192 typical value 2048 msgsys:msginfo_msgmnb default 4096 max 2147483647 2048 typical value 4096 msgsys:msginfo_msgmni default 50 max 2147483647 50 typical value 50 msgsys:msginfo_msgssz default 8 max 2147483647 8 msgsys:msginfo_msgtql default 40 max 2147483647 50 typical value 40 msgsys:msginfo_msgseg default 1024 max 32767 1024 D: scz /usr/include/sys/shm.h struct shminfo { size_t shmmax, /* max shared memory segment size */ shmmin; /* min shared memory segment size */ int shmmni, /* # of shared memory identifiers */ shmseg; /* max attached shared memory */ /* segments per process */ }; 在/etc/system文件中,共享内存统一使用 shmsys:shminfo_ 前缀 shmsys:shminfo_shmmax shmsys:shminfo_shmmin shmsys:shminfo_shmmni shmsys:shminfo_shmseg 可用nm -nx /dev/ksyms | grep '|shminfo'进行观察,也可用adb查看当前设置 echo 'shminfo_shmmax/E' | adb -k (SPARC/Solaris 8 64-bit kernel mode) echo 'shminfo_shmmni/D' | adb -k /usr/include/sys/sem.h /* * Semaphore information structure */ struct seminfo { int semmap; /* # of entries in semaphore map */ int semmni; /* # of semaphore identifiers */ int semmns; /* # of semaphores in system */ int semmnu; /* # of undo structures in system */ int semmsl; /* max # of semaphores per id */ int semopm; /* max # of operations per semop call */ int semume; /* max # of undo entries per process */ int semusz; /* size in bytes of undo structure */ int semvmx; /* semaphore maximum value */ int semaem; /* adjust on exit max value */ }; 在/etc/system文件中,信号灯统一使用 semsys:seminfo_ 前缀 semsys:seminfo_semmap semsys:seminfo_semmni semsys:seminfo_semmns semsys:seminfo_semmnu semsys:seminfo_semmsl semsys:seminfo_semopm semsys:seminfo_semume semsys:seminfo_semusz semsys:seminfo_semvmx semsys:seminfo_semaem 可用nm -nx /dev/ksyms | grep '|seminfo'进行观察,也可用adb查看当前设置 echo 'seminfo_semvmx/D' | adb -k (SPARC/Solaris 8 64-bit kernel mode) echo 'seminfo_semmni/D' | adb -k /usr/include/sys/msg.h /* * Message information structure. */ struct msginfo { int msgmap; /* # of entries in msg map */ int msgmax; /* max message size */ int msgmnb; /* max # bytes on queue */ int msgmni; /* # of message queue identifiers */ int msgssz; /* msg segment size (should be word size */ /* multiple) */ int msgtql; /* # of system message headers */ ushort_t msgseg; /* # of msg segments (MUST BE < 32768) */ }; 在/etc/system文件中,消息队列统一使用 msgsys:msginfo_ 前缀 msgsys:msginfo_msgmap msgsys:msginfo_msgmax msgsys:msginfo_msgmnb msgsys:msginfo_msgmni msgsys:msginfo_msgssz msgsys:msginfo_msgtql msgsys:msginfo_msgseg 在/etc/system文件中,设置语句形如 set shmsys:shminfo_shmmax = 0x2000000 7. DNS相关问题 7.1 如何进行DNS区传输 A: scz 用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 如何获知权威名字服务器 Q: bbs.whnet.edu.cn突然改换IP了,以前是202.112.20.132,现在是202.114.0.248。 我知道应该使用全称域名(FQDN)访问关键服务器,而非使用固定IP。问题在于刚刚改 换IP的那几个小时内,大部分地区的DNS Server因Cache的关系未完成同步,以致即 使我用全称域名(FQDN)访问,仍然指向原来的202.112.20.132。难道真要等待24小时 同步彻底完成吗? A: scz 2005-03-15 假设你怀疑某个具有合法全称域名(FQDN)的关键服务器突然改换IP,并且客户机周围 的DNS Server未完成同步。此时可以直接向管辖该全称域名的权威名字服务器提交A 记录查询以获取最准确的当前IP。举例如下: nslookup (在cmd.exe或bash中启动nslookup) > server (就近随便找一个有效DNS Server) > set type=ns > whnet.edu.cn (获知管辖该域的权威名字服务器) Non-authoritative answer: whnet.edu.cn nameserver = server20.hust.edu.cn whnet.edu.cn nameserver = dns.whnet.edu.cn whnet.edu.cn nameserver = sea.whnet.edu.cn server20.hust.edu.cn internet address = 202.114.0.242 dns.whnet.edu.cn internet address = 202.112.20.131 sea.whnet.edu.cn internet address = 202.112.20.133 > server 202.112.20.131 (切换到权威名字服务器) Default Server: dns.whnet.edu.cn Address: 202.112.20.131 > set type=a > bbs.whnet.edu.cn Name: bbs.whnet.edu.cn Address: 202.114.0.248 Q: 一个顺便想到的问题。有时会碰上单IP上多虚拟主机的情形,为访问这些虚拟主机, 不能在URL中使用那个惟一的IP,必须使用具体的FQDN。实际上就是HTTP请求报文中 需要有效指定"Host:"字段。换句话说,对于虚拟主机来讲,这两种请求并不等价: http:// http:// 万一碰上DNS故障或其它原因造成的困境,该FQDN不能解析成有效IP,意味着即使那 台服务器IP未改换、虚拟主机设置未改换,我也无法以WWW形式访问它。 A: scz 作为临时应急手段,可以编辑%systemroot%\system32\drivers\etc\hosts(Windows 用户)或/etc/hosts(Unix用户)文件,在该文件尾部增加如下行: 202.114.0.248 bbs.whnet.edu.cn 对于缺省安装的Unix、Windows系统,hosts文件的优先级高于DNS。此时在浏览器地 址栏中输入http://bbs.whnet.edu.cn,这样发送出去的HTTP请求报文已经有效指定 "Host:"字段,可以成功访问虚拟主机。 当然,bbs.whnet.edu.cn本身不是虚拟主机,不需要这样做,可以直接通过IP访问主 页。 7.3 如何配置DNS的委托解析 Q: 我想把子域DNS解析下放到下面去,在我这里如何配置 A: zhangql@SMTH 子域 IN NS <负责子域DNS解析的IP> 7.4 如何获知BIND的版本号 Q: 如何识别当前运行的bind是什么版本 A: M. Zuber dig @ txt chaos VERSION.BIND 或者 nslookup server set query=txt set class=chaos VERSION.BIND 但是这个返回结果可以通过/etc/named.conf自己设置,并不可靠。如果你正在运 行BIND 8,可以执行 /usr/sbin/ndc status D: scz 2002-11-27 16:30 BIND支持如下查询请求,参看src/bin/named/ns_req.c中的req_query()函数 dig @ txt chaos VERSION.BIND dig @ txt chaos HOSTNAME.BIND A: backend #! /bin/sh # bv (Bind Version) script # written by backend@nsfocus.com USAGE="Usage: $0
" 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 很多主机没有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@SMTH 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 -------------------------------------------------------------------------- 8. Solaris编程相关问题 8.0 Solaris多线程编程与errno全局变量 Q: 我正在进行Solaris多线程编程,如果errno是全局变量,则任意线程都可能修改 其值,而我需要判断errno,此时应该注意什么问题。 A: Casper H.S. Dik 2002-08-05 21:47 使用gcc -D_REENTRANT,此时errno是每个线程相关的,不再是全局变量。当正确地 包含之后,errno被重新定义过: extern int *___errno(); #define errno (*(___errno())) 这个函数返回一个指针,指向一个线程相关整数。注意,你仍然可以使用&errno。 8.1 Solaris内核模块中如何getcwd Q: 在Solaris 7 64-bit内核模块中如何获知一个进程的当前工作目录(cwd),getcwd 并不是一个系统调用 A: Rich Teer 最好通过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.3 如何避免一个套接字进入TIME_WAIT状态 Q: 我正在写一个unix server程序,不是daemon,经常需要在命令行上重启它,绝大多 数时候工作正常,但是某些时候会报告"bind: address in use",于是重启失败。 A: Andrew Gierth server程序总是应在调用bind()之前设置SO_REUSEADDR套接字选项。至于TIME_WAIT 状态,你无法避免,那是TCP协议的一部分。 Q: 如何避免等待60秒之后才能重启服务 A: Erik Max Francis 使用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状态,这些在<>中 2.6节解释很清楚了。 MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现 都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。 TIME_WAIT状态最大保持时间是2MSL,也就是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: 小四 在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。 # /usr/ccs/bin/nm -nx /dev/ksyms | grep tcp_param_arr [3682] |0x00001048e0e8|0x0000000004f8|OBJT |LOCL |0 |ABS |tcp_param_arr # skd64 0x00001048e0e8 64 byteArray [ 64 bytes ] ----> 0000000000000000 00 00 03 E8 00 09 27 C0-00 00 03 E8 00 00 00 00 0000000000000010 00 00 00 00 10 48 F0 00-00 00 00 01 FF FF FF FF 0000000000000020 00 00 00 80 00 00 00 00-00 00 00 00 10 48 F0 18 0000000000000030 00 00 00 00 FF FF FF FF-00 00 04 00 00 00 00 00 # echo "tcp_param_arr/X" | adb -kw /dev/ksyms /dev/mem physmem 3b72 tcp_param_arr: tcp_param_arr: 3e8 # 注意到下限0x3e8,也就是1000ms,最大0x927c0,也就是600s,10分钟。修改下限为 0,然后就可以用ndd设置当前值为0了。 # 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 我不知道这样做有什么灾难性后果,参看<>的声明。 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中。 D: scz 2003-12-22 12:58 对于Linux,如果存在/proc/sys/net/ipv4/tcp_fin_timeout文件,可以直接修改: echo 5 > /proc/sys/net/ipv4/tcp_fin_timeout 缺省值是60 A: 小四 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: 小四 看这样两个结构定义 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.32.8/iso-8859-1/CPPPG/Pragmas.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 "-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 我想你可以单独写一个pkt_header_read()函数,单字节读取然后拼装成相应的数 据类型。如果你想避免函数调用,可以使用"inline"关键字。 A: Casper H.S. Dik - Network Security Engineer 你是否意识到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 采用字符指针是正确的,但是你犯了个错误,编写如下函数 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 #include /* * 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 即可,与之相应的还有pshpack2.h、pshpack4.h。 2004-07-21 15:55 scz 正规用法应该是这样 #include struct { ... ... }; #include 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@APUE 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@APUE 我没见过这个写法,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: 首先执行如下shell命令 # echo 'shminfo_shmmax/D' | adb -k (SPARC/Solaris 2.6 32-bit kernel mode) physmem fddb shminfo_shmmax: shminfo_shmmax: 134217728 于是我们可以编写如下C代码 -------------------------------------------------------------------------- #include #include #include #include #include #include #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 最简单的就是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.8 如何单独获得Solaris编译环境 Q: 我需要安装哪些包 A: Seán Boran 需要下列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 执行 /usr/xpg4/bin/nm /platform/sun4u/kernel/unix | egrep 'OBJT \|GLOB' | more 显示结果中部分为Solaris内核可调参数,另外一些非可调内核参数。可以用ndd获取、 设置网络相关参数。 D: scz 可以考虑 /usr/ccs/bin/nm -nx /dev/ksyms | egrep 'OBJT \|GLOB' | more 不知道二者区别何在?第二个报告内容应该包含了后来动态加载内核模块输出的符号, 第一个才对应基本内核输出的符号。 8.10 如何获取自Unix纪元以来的秒数,如何转换成可理解的表达方式 A: scz 问题是针对shell script以及SLKM编程来的,顺带给一个标准C应用层编程的说明 man -s 2 time(SPARC/Solaris 7) -------------------------------------------------------------------------- 系统调用 time(2) 名字 time - 获取时间 摘要 #include #include time_t time ( time_t *tloc ); 描述 time()函数返回自UTC 1970-01-01 00:00:00以来的秒数 如果tloc非NULL,返回值同时存放在tloc所对应的空间中。如果tloc指针非法, time()调用失败。注意,tloc可以为NULL。 返回值 成功则返回秒数 失败则返回( time_t )-1,同时设置errno 属性 异步信号安全的 参看 stime(2) ctime(3C) attributes(5) -------------------------------------------------------------------------- A: scz & deepin 2001-05-30 00:03 国际标准时间(Coordinated Universal Time)UTC 1970-01-01 00:00:00 称为 Unix Epoch(Unix纪元)。 在Red Hat Linux下 $ date -u --date='20010530 22:48:10' +%s 991234090 <-- 这个就是自Unix纪元以来的秒数 $ perl -le 'print scalar localtime 991234090' Wed May 30 22:48:10 2001 $ 在Solaris 7 64-bit kernel mode下 # netstat -k | grep boot_time avenrun_5min 1 avenrun_15min 3 boot_time 959048950 # ~~~~~~~~~ 这个也是自Unix纪元以来的秒数 # nm -nx /dev/ksyms | grep boot_time [10161] |0x000010445090|0x000000000008|OBJT |GLOB |0 |ABS |boot_time # skd64 0x10445090 8 byteArray [ 8 bytes ] ----> 0000000000000000 00 00 00 00 39 29 EC F6 # echo "boot_time /J" | adb -kw /dev/ksyms /dev/mem physmem 3b72 boot_time: boot_time: 3929ecf6 <-- 0x3929ecf6 == 959048950 # uptime 下午10:59 运行 8 天 4:31, 4 users, 平均负荷: 0.01, 0.01, 0.01 # date 2000年05月30日 星期二 23时00分12秒 GMT # perl -le 'print scalar localtime 0x3929ecf6' Mon May 22 18:29:10 2000 # nm -nx /dev/ksyms | grep "|gtime" [9618] |0x0000100ed380|0x00000000000c|FUNC |GLOB |0 |ABS |gtime # 如果要在程序中使用boot_time,可以 extern time_t boot_time; /* netstat -k | grep boot_time */ 这个boot_time指明了系统最近一次重启成功的时间,以自Unix纪元以来的秒数表示。 /usr/include/sys/time.h 中定义了 typedef long time_t; /* time of day in seconds */ 意味着在Solaris 7 64-bit kernel mode中,time_t是64-bit的,而在Solaris 2.6 中是32-bit的。 下面分别来自7和2.6的源码: -------------------------------------------------------------------------- /* Solaris 7 */ time_t gtime ( void ) { return( hrestime.tv_sec ); } /* Solaris 2.6 */ int gtime () { return( hrestime.tv_sec ); } -------------------------------------------------------------------------- 从SLKM调试信息中看到gtime()返回的是类似0x3934aea4这样的值,显然这是自Unix 纪元开始计的秒数。如果要在自己的SLKM里使用内核函数gtime(),可以这样: #if SOLARIS2 == 7 extern time_t gtime ( void ); #elif SOLARIS2 == 6 extern int gtime ( void ); #endif 显然我并不推荐直接使用内核变量hrestime.tv_sec,尽管内核输出了hrestime变量。 uptime(1)命令最终也使用了内核函数gtime()。由于Solaris的date命令并不支持 $ date -u --date='20010530 22:48:10' +%s 这种用法是GNU扩展。为了广泛兼容地获取自Unix纪元以来的秒数: 在Solaris 7下 # truss -t time uptime 2>&1 | head -1 | awk '{print $3;}' 959758372 <-- 10进制 # echo c 959758372 16o p c | /usr/bin/dc 3934C024 <-- 16进制 # echo c 16i 3934C024 p c | /usr/bin/dc 959758372 # 在Redhat Linux下 # strace -e trace=time uptime 2>&1 | head -1 | awk '{print $3;}' 991235978 # echo c 991235978 16o p c | /usr/bin/dc 3B150F8A # echo c 16i 3B150F8A p c | /usr/bin/dc 991235978 # 这篇QA的主要目的是介绍SLKM编程中可以使用gtime()获取当前时间,其他技巧顺带 介绍而已,希望对大家有所启发。 D: 小四 顺便记录一下各进制之间的转换 $ echo c 8 8o p c | /usr/bin/dc 10 <-- 10进制转8进制 $ echo c 17 16o p c | /usr/bin/dc 11 <-- 10进制转16进制 $ echo c 8i 11 12o p c | /usr/bin/dc 9 <-- 8进制转10进制 $ echo c 8i 17 20o p c | /usr/bin/dc F <-- 8进制转16进制 $ echo c 16i FF Ao p c | /usr/bin/dc 255 <-- 16进制转10进制 $ echo c 16i 10 8o p c | /usr/bin/dc 20 <-- 16进制转8进制 $ 8.11 如何页边界对齐式分配内存 Q: 我希望在页边界上分配大块内存,要求普通用户、非特权进程亦能使用此技术。 在mmap(2)手册页中没有明确表明返回地址边界对齐。它提到可以指定起始地址以 保证页边界对齐,但没有说明如果由系统选定起始地址时是否也是页边界对齐的。 MAP_ANON并非所有系统都支持,我需要在Solaris 2.x上运行。 A: Andrew Gierth mmap(2)即可满足要求。某些系统提供了valloc或者memalign,但它们的实现机制是, 分配超过请求大小的内存,然后调整之,这相当浪费。 mmap(2)应该始终是页边界对齐的。 在那些不支持 MAP_ANON 的系统上,打开/dev/zero获取句柄,传递给mmap(2),效果 是一样的。 mmap(2)的可移植性足够好,不过"分配超过请求大小的内存并调整之"可能更具有可 移植性。 8.12 Solaris下究竟如何使用setuid/seteuid/setreuid Q: 我被setuid/seteuid/setreuid搞疯了,到底怎么使用它们? D: tt 2001-06-07 14:04 如果一个Solaris下的程序setuid-to-,应该尽量使用setreuid()永久放弃特权, 而不是setuid(),因为这样最通用。尤其当setuid-to-的时候,setuid() 根本无法永久放弃特权。 如果一个Solaris下的程序setgid-to-,应该尽量使用setregid()永久放弃特权, 而不是setgid(),因为这样最通用。尤其当整个过程中无法满足EUID为0的时候, setgid()根本无法永久放弃特权。 假设一个Solaris下的程序同时setuid、setgid过,较理想的释放顺序应该是先释放 setgid特权,后释放setuid特权。 据tt报告,Linux下setuid()实现和Solaris明显不同,无论如何都同时设置RUID、 EUID、SUID(假设权限允许),注意区分不同系统下系统调用setuid()的不同表现。 D: tt 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 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o reg reg.c -lgen */ #include #include #include 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系统中如何检查内存泄露、腐烂 Q: 在一台Sun服务器上运行了好多程序,运行一段日子后发现内存空闲少了几百兆,基 本上都用光了,怎么知道是那些程序干的。 A: lose@SMTH 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 #include #include #include #include #include #include #include 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]闭区间上的特权端口 A: 小四 下面以SPARC/Solaris 2.6为测试对象 # ndd -get /dev/tcp tcp_smallest_nonpriv_port 1024 (缺省值为1024) # /usr/ccs/bin/nm -nx /dev/ksyms | grep tcp_param_arr [4165] |0x60412ee4|0x00000330|OBJT |LOCL |0 |ABS |tcp_param_arr # skd 0x60412ee4 128 byteArray [ 128 bytes ] ----> 00000000 00 00 03 E8 00 09 27 C0-00 03 A9 80 60 41 3A 14 ... 00000010 00 00 00 01 FF FF FF FF-00 00 00 80 60 41 3A 2C .... 00000020 00 00 00 00 FF FF FF FF-00 00 10 00 60 41 3A 40 .... 00000030 00 00 00 01 00 00 04 00-00 00 00 01 60 41 3A 54 ............ 00000040 00 00 00 01 00 00 4E 20-00 00 01 F4 60 41 3A 68 ...... 00000050 00 00 00 80 40 00 00 00-00 04 00 00 60 41 3A 80 ... 00000060 00 00 00 00 00 00 00 0A-00 00 00 00 60 41 3A 90 ............ 00000070 00 00 04 00 00 00 80 00-00 00 04 00 60 41 3A 9C ...... # skd 0x60413a9c 32 byteArray [ 32 bytes ] ----> 00000000 74 63 70 5F 73 6D 61 6C-6C 65 73 74 5F 6E 6F 6E tcp_smallest_non 00000010 70 72 69 76 5F 70 6F 72-74 00 00 00 74 63 70 5F priv_port...tcp_ # echo "tcp_param_arr+0t112,4 /naX" | adb -k /dev/ksyms /dev/mem physmem 3d1f tcp_param_arr+0x70: tcp_param_arr+0x70: 400 <-- min ( u_int ) tcp_param_arr+0x74: 8000 <-- max ( u_int ) tcp_param_arr+0x78: 400 <-- value ( u_int ) tcp_param_arr+0x7c: 60413a9c <-- name ( char * ) # echo "tcp_param_arr+0t112 /W 0t1" | adb -kw /dev/ksyms /dev/mem physmem 3d1f tcp_param_arr+0x70: 0x400 = 0x1 # ndd -set /dev/tcp tcp_smallest_nonpriv_port 1 但是普通用户/usr/sbin/inetd -s -t启动后,仍然无法telnet localhost 7,说明 [1, 1023]的限制不仅仅在于这个内核参数。 8.17 SPARC/Solaris 7 64-bit kernel mode下dumpadm(1M)手册页 A: 小四 2001-11-02 00:04 -------------------------------------------------------------------------- 维护命令 dumpadm(1M) 名字 dumpadm - 配置操作系统crash dump时的行为 摘要 /usr/sbin/dumpadm [ -nuy ] [ -c content-type ] [ -d dump-device ] [-m min k | min m | min% ] [ -s savecore-dir ] [ -r root-dir ] 描述 dumpadm是一个管理命令,用于配置操作系统crash dump时的行为。crash dump 指发生致命系统错误时将计算机物理内存dump到磁盘设备上,一般此时主控台( console)上会显示一条描述错误的消息,接着系统开始做一次crash dump,写 入预先指定的dump device。典型的dump device是一个本地磁盘分区。dumpadm 可以指定dump device。crash dump完成(写入dump device)后,系统才开始 reboot。 这种reboot过程中,将自动执行savecore(1M)命令,以便从dump device中获取 crash dump,并以一对文件的形式(unix.X、vmcore.X)写入普通文件系统。这 里X是一个整数,用于区分不同的crash dump。存放unix.X、vmcore.X的目录也 可由dumpadm命令配置。 缺省dump device对应一个适当的swap分区,参看swap(1M)手册页。 为了获取当前crash dump配置,不带任何参数直接执行dumpadm # dumpadm Dump content: kernel pages Dump device: /dev/dsk/c0t0d0s1 (swap) Savecore directory: /var/crash/ Savecore enabled: yes # 这个设置是只dump内核内存页、dump device是一个swap磁盘分区,savecore目 录设置到/var/crash/,reboot过程中自动运行savecore(1M)命令。 只有root才能观察、修改crash dump设置。 -c content-type kernel 只dump内核内存页 all dump所有内存页 -d dump-device dump-device 比如/dev/dsk/cNtNdNsN swap 如果指定"swap",dumpadm检查活动swap entries并从中 挑选最适合的entry用于dump device,参看swap(1M)。 -m min k | min m | min% 在当前savecore-dir中创建一个minfree文件,以指明savecore时至 少保证savecore-dir所在文件系统拥有这么多数量的自由磁盘空间。 min参数如下 k 正整数,KB单位 m 正整数,MB单位 % savecore-dir所在文件系统总容量的百分比 如果在产生unix.X、vmcore.X之前,minfree文件已经存在,savecore将与之协 商。如果savecore的结果导致自由磁盘空间违背minfree设置,将不会产生 unix.X、vmcore.X文件,一条错误信息被送往日志系统。管理员应该立即清理 savecore目录以提供足够的自由磁盘空间,并重新手动执行savecore指令。管 理员可以在savecore命令行上临时指定其他目录用于生成unix.X、vmcore.X。 -n 设置reboot过程中不自动运行savecore。并不推荐这样设置。如果 dump device是一个swap分区,crash dump将在系统开始swap时被覆 盖。如果reboot后不尽快执行savecore,就不大可能成功获取crash dump了。 -r root-dir 指定dumpadm创建文件时使用的根目录。如果未指定-r参数,缺省根 目录是"/" -s savecore-dir 指定存放unix.X、vmcore.X的目录,必须是已经存在的绝对路径。 如果reboot过程中该目录不存在,执行savecore之前会自动建立它。 参看"注意"部分,与savecore-dir相关的安全问题。缺省设置是 /var/crash/,uname -n看看。 -u 根据/etc/dumpadm.conf更新crash dump设置。启动脚本 /etc/init.d/savecore会用到/etc/dumpadm.conf。如果该文件不存 在或者参数无效,将采用缺省设置。 看这个意思,每次手动配置crash dump行为都会同步更新这个配置文 件? # cat /etc/dumpadm.conf # # dumpadm.conf # # Configuration parameters for system crash dump. # Do NOT edit this file by hand -- use dumpadm(1m) instead. # DUMPADM_DEVICE=/dev/dsk/c0t0d0s1 DUMPADM_SAVDIR=/var/crash/sun27 DUMPADM_CONTENT=kernel DUMPADM_ENABLE=yes # -y 设置reboot过程中自动运行savecore,这也是缺省设置。 举例 重新配置dump device到一个专用dump device # dumpadm -d /dev/dsk/c0t2d0s2 Dump content: kernel pages Dump device: /dev/dsk/c0t2d0s2 (dedicated) Savecore directory: /var/crash/ Savecore enabled: yes # 退出状态 0 成功 1 获取、修改crash dump配置时发生了致命错误。 2 无效命令行参数 文件 /dev/dump /etc/init.d/savecore /etc/dumpadm.conf savecore-directory/minfree 参看 uname(1) savecore(1M) swap(1M) attributes(5) 注意 Dump Device Selection dumpadm -d swap时,试图选择最大的swap block device做dump device。如果 没有block device用于swap,则选择最大的swap entry做dump device。如果不 存在swap entries,或者存在但无法配置成dump device,会显示一条警告信息。 本地和远程swap files也可以配置成dump device,但不推荐这样做。 Dump Device/Swap Device Interaction 如果dump device同时是swap device,而后来管理员用swap -d删除了这个swap device,此时swap命令会自动执行dumpadm -d swap,试图使用其它swap device 做dump device。如果因种种原因没有成功,将禁止crash dump,然后显示警告 信息。类似的,如果crash dump处在disable状态,管理员用swap -a增加新swap device,此时将自动激活dumpadm -d swap,enable crash dump,就用新swap device做为dump device。 成功执行dumpadm -d swap后,将同步更新/etc/dumpadm.conf文件,下次启动 会使用这个配置文件。如果这之后有更大、更适合的swap device出现,并不会 自动修改dump device设置。管理员可以重新执行dumpadm -d swap来完成dump device的修改。 Minimum Free Space 假设dumpadm -m时使用了百分比,而之后文件系统大小改变,并不会自动重新 计算这个值,管理员应该重新执行dumpad -m完成调整。 如果savecore-dir中没有minfree文件,savecore缺省保留1MB的自由空间。如 果minfree文件大小为0,才意味着不需要保留自由空间。 Security Issues reboot过程中指定savecore-dir不存在,将自动创建之,0700,属主root。由 于crash dump中包含安全相关的信息,所以要注意savecore-dir的权限设置。 -------------------------------------------------------------------------- 8.18 9. 图形界面相关问题 9.1 如何避免进入Solaris的图形界面 Q: 我想让console保持在字符模式下,该如何做 A: Darren Dunham 这里有一份很好的FAQ,http://www.wins.uva.nl/pub/solaris/solaris2.html 下文引自http://www.science.uva.nl/pub/solaris/solaris2.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: 更省事的办法是 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: 应该是dtscreen 9.3 如何调整键盘重复率 Q: Ultra 5 Solaris 8 如何设置键盘重复率?我想设置重复率到最大,延迟到最小。 A: Alan Coopersmith 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 /etc/openwin/server/etc/OWconfig /usr/openwin/server/etc/OWconfig A: hycan@SMTH /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 : 上面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 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: 小四 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 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 -------------------------------------------------------------------------- /* * display info about network interfaces */ #include #include #include #include #include #include #include #include #include #include /* * 为什么要这样定义宏,直接做强加型转换不好么 */ #define inaddrr( x ) ( *( struct in_addr * )&ifr->x[ sizeof( sa.sin_port ) ] ) /* * 注意,这里IFRSIZE是变化的,依赖于size的取值 */ #define IFRSIZE ( ( int )( size * sizeof( struct ifreq ) ) ) int main ( int argc, char * argv[] ) { unsigned char *u; int sockfd, size = 1; struct ifreq *ifr; struct ifconf ifc; struct sockaddr_in sa; if ( ( sockfd = socket( PF_INET, SOCK_DGRAM, IPPROTO_IP ) ) < 0 ) { 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 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 */ free( ifc.ifc_req ); close( sockfd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- A: scz 上例在Linux下gcc -Wall -pipe -O3 -s -o getinfo getinfo.c编译即可。 # ifconfig eth0 192.168.10.1 netmask 255.255.0.0 broadcast 192.168.255.255 # ifconfig eth0:1 192.168.10.11 netmask 255.255.0.0 broadcast 192.168.255.255 # ./getinfo Interface: lo IP Address: 127.0.0.1 Interface: eth0 IP Address: 192.168.10.1 HW Address: 00:10:14:ff:ff:ff Netmask: 255.255.0.0 Broadcast: 192.168.255.255 MTU: 1500 Metric: 0 Interface: eth0:1 IP Address: 192.168.10.11 HW Address: 00:10:14:ff:ff:ff Netmask: 255.255.0.0 Broadcast: 192.168.255.255 MTU: 1500 Metric: 0 # 如果用于SPARC/Solaris,需要修改一些地方,最主要的是如何获取本机MAC地址。然 后用如下命令编译 gcc -Wall -pipe -O3 -o getinfo getinfo.c -lsocket -lnsl -------------------------------------------------------------------------- /* * display info about network interfaces */ #include #include #include #include #include #include /* * 对于Solaris移植,必须包含该头文件 */ #include #include #include #include #include #define inaddrr( x ) ( *( struct in_addr * )&ifr->x[ sizeof( sa.sin_port ) ] ) /* * 注意,这里IFRSIZE是变化的,依赖于size的取值 */ #define IFRSIZE ( ( int )( size * sizeof( struct ifreq ) ) ) int main ( int argc, char * argv[] ) { unsigned char *u; int sockfd, size = 1; struct ifreq *ifr; struct ifconf ifc; struct sockaddr_in sa; if ( ( sockfd = socket( PF_INET, SOCK_DGRAM, IPPROTO_IP ) ) < 0 ) { 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 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的联合中没有定义相应成员, * 所以用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 */ free( ifc.ifc_req ); close( sockfd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 这里演示的技术适用于普通用户,不再需要root权限ifconfig -a查看本机MAC地址。 A: 小四 2001-12-18 00:49 前面演示了x86/Linux、SPARC/Solaris上如何获取某些网卡配置信息,昨天发现在 x86/FreeBSD上大有不同,确切地说,是Berkeley-derived实现与System V实现有不 少区别,更多细节请参看<>卷I的16.6节以及17章。重 写代码如下 -------------------------------------------------------------------------- /* * Copyright (c) 1983, 1993 * The Regents of the University of California. All rights reserved. * ----------------------------------------------------------------------- * * gcc -static -Wall -pipe -O3 -s -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 #include #include #include #include /* 使用getopt(3) man -S 3 getopt */ #include /* for errx() */ #include /* for htonl() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /************************************************************************ * * * 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 ]\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; /* * 参看<> 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 */ /************************************************************************/ -------------------------------------------------------------------------- D: 小四 2001-12-19 17:26 对于FreeBSD,用ioctl( , SIOCGIFADDR, )可以获取指定接口的主IP,但是无法获取 指定接口的IP Alias,如有此类需求,请使用前面演示的sysctl()。我不确认使用 SIOCGIFADDR是否一定无法获取IP Alias,如果哪位朋友知道,麻烦指点一下。 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o freebsd_getinfo freebsd_getinfo.c * * 保留这个程序仅仅为了测试和演示,不要真正使用它 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define inaddrr( x ) ( *( struct in_addr * )&ifr.x[ sizeof( sa.sin_port ) ] ) /* * 注意,这里IFRSIZE是变化的,依赖于size的取值 */ #define IFRSIZE ( ( int )( size * sizeof( struct ifreq ) ) ) int main ( int argc, char * argv[] ) { int sockfd, lastlen, size = 0; struct ifreq ifr; struct ifconf ifc; struct sockaddr_in sa; if ( argc == 1 ) { /* * errx( EXIT_FAILURE, "\nUsage: %s ", argv[0] ); */ fprintf( stderr, "Usage: %s \n", argv[0] ); return( EXIT_FAILURE ); } if ( 0 > ( sockfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) ) { fprintf( stderr, "Cannot open socket\n" ); return( EXIT_FAILURE ); } /* * ifc.ifc_buf */ ifc.ifc_req = NULL; lastlen = 0; do { size += 10; /* * 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; fprintf( stderr, "size = %d ifc.ifc_len = %d\n", size, ifc.ifc_len ); /* * 对于源自Berkeley的实现,即使这里没有返回错误,也不能简单认为无 * 误,参看<> vol I 16.6节 */ if ( ioctl( sockfd, SIOCGIFCONF, &ifc ) < 0 ) { perror( "ioctl SIOCFIFCONF" ); exit( EXIT_FAILURE ); } else { fprintf( stderr, "> size = %d ifc.ifc_len = %d\n", size, ifc.ifc_len ); if ( lastlen == ifc.ifc_len ) { break; } else { lastlen = ifc.ifc_len; } } } while ( 1 ); fprintf( stderr, "size = %d lastlen = %d\n", size, lastlen ); free( ifc.ifc_req ); memset( &ifr, 0, sizeof( struct ifreq ) ); strncpy( ifr.ifr_name, argv[1], IFNAMSIZ ); /* * 只能获取指定接口的主IP */ if ( ioctl( sockfd, SIOCGIFADDR, &ifr ) < 0 ) { perror( "ioctl" ); return( EXIT_FAILURE ); } fprintf( stderr, "\n" ); fprintf( stderr, "Interface : %s\n", ifr.ifr_name ); fprintf( stderr, "IP : %s\n", inet_ntoa( inaddrr( ifr_addr.sa_data ) ) ); close( sockfd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- A: Sun Microsystems 2001-01-03 如果不想通过ARP表获取MAC地址,就只能通过DLPI实现。此时必须以root身份运行该 程序。 -------------------------------------------------------------------------- /* * Usage : * * 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 #include #include #include #include #include #include #include #include #include #include #include #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 : \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 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 不需要修改"pcn.conf"文件。开始我只是"touch /reconfigure",Solaris x86检测 到了网卡,但是"ifconfig -a"的时候只有loopback接口,于是我尝试如下步骤: # drvconfig # devlinks # touch /reconfigure 重启动后一切ok D: scz 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 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 有三个文件需要注意,/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.文件,比如/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 - <>。 如果因为配置虚拟接口出现不期望的路由,考虑手动"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启动脚本中去。 如果需要增加的虚拟网络接口比较多,可以参看Infodoc ID - 16369中提供的启动脚 本。 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 " 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 需要修改如下文件 /etc/hosts /etc/hostname. /etc/nodename /etc/net/*/hosts (3 files, man -s 7D ticotsord) 如果你运行在VxVM下,则应该 # vxdctl hostid # vxdctl init 10.6 SPARC/Solaris 2.5/2.6/7/8下如何设置网卡100Mb全双工 Q: 我从SPARC连接3Com交换机时,总是使用半双工,如何配置网卡强行使用100Mb全 双工 A: Martin Scerri 下列回答来自"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 你可以用如下命令获取当前设置 # 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 下面以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手册 A: 小四 man -S 8 ifconfig,下面是x86/FreeBSD 4.3-RELEASE的ifconfig(8)手册页 -------------------------------------------------------------------------- IFCONFIG(8) FreeBSD系统管理员手册 IFCONFIG(8) 名字 ifconfig - 配置网络接口参数 摘要 ifconfig [-L] interface [address_family] [address [dest_address]] [parameters] ifconfig -a [-L] [-d] [-u] [address_family] ifconfig -l [-d] [-u] [address_family] ifconfig [-L] [-d] [-u] 描述 address 对于inet、inet6地址族来说,address既可以是一个来自/etc/hosts 的主机名(参看hosts(5)),也可以是点分十进制的IP地址 测试表明,一旦在ifconfig命令行中设置接口主IP,必然导致网络接 口自动UP,所以如果要down一个接口,务必留神这里。 address_family 指定地址族,目前支持五种,inet inet6 atalk ether ipx 很多时候并不需要明确指定地址族,比如没有明显歧义的时候。 dest_address 点到点链接中,dest_address指定对端地址 interface 参数格式是一个字符串(name unit),比如"en0" parameters add 等价于alias,为保持与BSD/OS的兼容而引入 alias 为该接口增加一个额外的网络层地址(比如IP地址)。如果这个地址与 该接口的第一个网络层地址在同一个子网内,必须为其指定子网掩码 0xffffffff(译注:稀奇,回头要测试) 注意,仅当ip alias与本接口主IP地址在同一子网时,才指定掩码为 0xffffffff。换句话说,不在同一子网时,没这个限制。问题是不在 同一子网,指定这个ip alias做什么? -alias 删除alias anycast (Inet6 only) Specify that the address configured is an anycast address. Based on the current specification, only routers may configure anycast addresses. Anycast address will not be used as source address of any of outgoing IPv6 packets. arp 允许使用ARP协议完成网络层地址与数据链路层之间的转换(这是缺省 设置)。目前实现了IP地址到10Mb/s以太网MAC地址之间的转换。(译注: 这份文档没更新吧) -arp 禁止使用ARP协议 broadcast 只对inet地址族有效,指定广播地址。缺省广播地址是IP地址主机部 分全1 debug 允许驱动程序相关的调试代码,通常是导致额外的错误日志写到主控 台 -debug 禁止驱动程序相关的调试代码 delete 等价于 -alias down 标识接口为down。此时系统不会通过该接口发送报文,如果可能,该 接口同时被重置为禁止接收报文。但是这个动作不会自动禁止那些使 用了该接口的路由表项(这里要测试)。 lladdr addr 设置接口的数据链路层地址。addr形如xx:xx:xx:xx:xx:xx。如果使用 这个选项时接口己经UP,则自动先DOWN后UP,以确保底层以太网卡的 接收过滤机制被重新编程过。 media type 如果驱动程序支持介质选择,可以使用该选顼设置接口所使用的介质 类型。一些接口支持几个不同物理介质之间的互斥使用。比如, 10Mb/s以太网接口可能支持AUI或非屏蔽双绞线,设置type为 "10base5/AUI",将切换当前活动连接到AUI port上,设置type为 "10baseT/UTP",将切换当前活动连接到双绞线。 缺省是autoselect,对于fxp0这种驱动来说,可能还有100baseTX、 10baseT/UTP mediaopt opts 如果驱动程序支持介质选择,才可使用该选项。opts是用逗号分隔的 选项列表。 比如 media 100baseTX mediaopt full-duplex 如果media指定了autoselect,mediaopt不用指定 对于fxp0这种驱动来说,如果仅仅指定了media 100baseTX而未指定 mediaopt full-duplex,看这样子是认为处在100Mb/s的半双工状态。 -mediaopt opts 如果驱动程序支持介质选择,则在该接口上禁止指定的介质选项。 metric n 缺省是0。metric为路由协议所用,参看routed(8)。metric值越高, 路由表项的优先级越低。 mtu n 设置接口的最大传输单元为n,这个缺省值是接口相关的。mtu用于限 制通过接口传输的报文大小。不是所有的接口支持设置mtu。 netmask mask 只对inet地址族有效。mask可以是0x前导的单个16进制数、点分十进 制,或者一个伪网络名(参看network(5))。掩码定义中不要求1是连续 的,但建议1是连续的。 remove 等价于 -alias ,为保持与BSD/OS的兼容而引入 up 标识接口为up。与ifconfig down相对应。设置接口的第一个网络层地 址(IP)时自动up。如果接口先down后up,导致网卡硬件被重新初始化。 如果没有提供可设置参数,ifconfig将显示当前配置。如果指定了协议族, ifconfig将只报告指定协议族的详细信息。 如果驱动程序支持介质选择,被支持的介质列表将出现在输出中 If -L flag is supplied, address lifetime is displayed for IPv6 addresses, as time offset string. -a 将显示所有接口信息。隐含设置 -d 显示down了的接口信息 -u 显示up了的接口信息 -l 用于列出系统中所有可用接口。如果要搭配使用,只能和-d、-u分别搭配。 不能和其它选项搭配使用。 只有超级用户才能修改网络接口配置 注意 介质选择相当新,只有少量驱动程序支持它,或者说需要它。 参看 netstat(1)、netintro(4)、rc(8)、routed(8) 历史 ifconfig命令源自4.2 BSD -------------------------------------------------------------------------- A: scz 下面举例说明FreeBSD 4.3-RELEASE中ifconfig的使用 1) 修改fxp0接口(00:90:0b:00:da:b1)的MAC地址 # ifconfig fxp0 ether lladdr 00:11:11:11:11:11 # ifconfig fxp0 ether fxp0: flags=8843 mtu 1500 ether 00:11:11:11:11:11 2) 在fxp0接口(192.168.200.1)上设置另外两个IP Alias(192.168.200.2/3) # ifconfig fxp0 inet alias 192.168.200.2 netmask 0xffffffff # ifconfig fxp0 inet fxp0: flags=8843 mtu 1500 inet 192.168.200.1 netmask 0xffff0000 broadcast 192.168.255.255 inet 192.168.200.2 netmask 0xffffffff broadcast 192.168.200.2 # ifconfig fxp0 inet alias 192.168.200.3 netmask 0xffffffff # ifconfig fxp0 inet fxp0: flags=8843 mtu 1500 inet 192.168.200.1 netmask 0xffff0000 broadcast 192.168.255.255 inet 192.168.200.2 netmask 0xffffffff broadcast 192.168.200.2 inet 192.168.200.3 netmask 0xffffffff broadcast 192.168.200.3 3) 同时修改fxp0接口的IP地址和MAC地址,并增加IP Alias # ifconfig fxp0 <-- 注意下面的输出,fxp0的驱动程序支持介质选择 fxp0: flags=8843 mtu 1500 inet 192.168.200.1 netmask 0xffff0000 broadcast 192.168.255.255 ether 00:90:0b:00:da:b1 media: autoselect (100baseTX) status: active supported media: ... ... 把下面三条命令放入一个shell script中执行 -------------------------------------------------------------------------- #! /bin/sh # fxp0.sh for x86/FreeBSD 4.3-RELEASE # nohup ./fxp0.sh & # # 动态修改IP、MAC # /sbin/ifconfig fxp0 192.168.254.1 netmask 255.255.0.0 broadcast \ 192.168.255.255 metric 0 mtu 1500 media 100baseTX mediaopt full-duplex \ up lladdr 00:90:0b:00:da:00 # # 增加IP Alias。这条不能和上条放一起,只能单独来一条命令 # /sbin/ifconfig fxp0 inet alias 192.168.0.4 netmask 0xffffffff # # 动态修改IP地址过程中,血的教训,务必clear arp cache # /usr/sbin/arp -da > /dev/null 2>&1 -------------------------------------------------------------------------- # ifconfig fxp0 fxp0: flags=8843 mtu 1500 inet 192.168.254.1 netmask 0xffff0000 broadcast 192.168.255.255 inet 192.168.0.4 netmask 0xffffffff broadcast 192.168.0.4 ether 00:90:0b:00:da:00 media: 100baseTX status: active supported media: ... ... 此时用192.168.254.1/192.168.0.4均可正常访问网络服务。如果不清空arp cache, 潜在存在很多问题,比如DNS Server以及网关无法访问,反向域名解析的延时等等。 telnet/ssh受反向域名解析延时的影响,ping/https不受此影响。 可以简化一下,比如media缺省使用autoselect(对FTP效率影响较大)。ifconfig会自 动根据IP地址和子网掩码计算广播地址,除非你需要指定一个奇怪的broadcast地址。 至于mtu、metric都可以不去招惹。 用这个shell script恢复原设置 -------------------------------------------------------------------------- #! /bin/sh # recover_fxp0.sh for x86/FreeBSD 4.3-RELEASE # nohup ./recover_fxp0.sh & # # 动态恢复IP、MAC # /sbin/ifconfig fxp0 192.168.200.1 netmask 255.255.0.0 media autoselect \ up lladdr 00:90:0b:00:da:b1 # # 删除IP Alias。这条不能和上条放一起,只能单独来一条命令 # /sbin/ifconfig fxp0 inet -alias 192.168.0.4 netmask 0xffffffff # # 动态恢复IP地址过程中,务必clear arp cache # /usr/sbin/arp -da > /dev/null 2>&1 -------------------------------------------------------------------------- # ifconfig fxp0 fxp0: flags=8843 mtu 1500 inet 192.168.200.1 netmask 0xffff0000 broadcast 192.168.255.255 ether 00:90:0b:00:da:b1 media: autoselect (100baseTX) status: active supported media: ... ... D: scz 2001-09-25 00:35 fxp0、xl0(3Com)这两种驱动可以动态修改MAC地址,rl0这种驱动不能,看样子是不 支持这些修改? rl0修改MAC地址后,外部可以看到这个MAC地址,但无法通信,比如ping不通,可能 是网卡驱动不支持的缘故,比如网卡的Working Address Register已经被修改,而驱 动中相应数据结构未被修改,就会出现这种现象。 D: scz 2004-02-11 09:53 1) Linux Linux下ifconfig修改MAC地址前必须先down掉相应接口,改了MAC之后再 up。但是Linux下将MAC设置成全零后(此时无错误提示),相应接口up失败: ifconfig eth0 down ifconfig eth0 hw ether 00:00:00:00:00:00 ifconfig eth0 up Linux虽然自身无法设置全零MAC,但可与全零MAC的系统正常通信。 2) x86/Solaris x86/Solaris 9不必down/unplumb接口,可直接修改MAC地址: ifconfig dnet0 ether 00:00:00:00:00:00 全零MAC地址可与同一HUB上的Windows系统通信。 3) Windows 98/NT/2000/XP/2003 Windows XP通过GUI界面设置全零MAC时无错误提示,但真实通信时仍然使用原MAC, "ipconfig -all"查看得到的MAC地址也是原MAC。 Windows 98/2000/XP都可与全零MAC的系统正常通信,NT/2003未测试,应该也是可以 的。 4) x86/FreeBSD FreeBSD下ifconfig修改MAC地址前不必down掉相应接口,可直接修改,并且全零MAC 地址与同一HUB上的Windows系统通信无误: ifconfig lnc0 ether lladdr 00:00:00:00:00:00 各种系统是否接受全零MAC地址是实现相关的,并不统一。注意,上述系统不但在同 一子网,而且在同一HUB上。一般交换机不接受全零MAC地址,导致ARP解析失败,无 法获取目标MAC,IP通信自然也就失败。 10.10 FreeBSD下arp的man手册 A: 小四 man -S 8 arp,下面是x86/FreeBSD 4.3-RELEASE的arp(8)手册页 -------------------------------------------------------------------------- ARP(8) FreeBSD系统管理员手册 ARP(8) 名字 arp - 地址解析协议 摘要 arp [-n] hostname arp [-n] -a arp -d hostname [proxy] arp -d -a arp -s hostname ether_addr [temp] [pub] arp -S hostname ether_addr [temp] [pub] arp -f filename 描述 参看arp(4)手册页。直接执行arp hostname命令,显示当前arp cache中对应 hostname的表项,hostname可以是名字,也可以是点分十进制IP地址。 -a 显示、删除所有ARP表项 -d 超级用户可以删除单个或所有ARP表项。如果arp -s时使用了pub关键 字,删除时可能需要指定proxy关键字。-d -a将删除所有ARP表项 -n 以数字方式显示网络层地址,否则试图反向解析IP地址后显示 -s hostname ether_addr 建立ARP表项。MAC地址形如xx:xx:xx:xx:xx:xx。如果不指定temp关键 字,这个表项将是真正的永久静态ARP表项。 如果使用了pub关键字,称这个表项是"published",此时系统扮演一 个ARP Server,即使这个hostname不对应自身,也响应针对这个 hostname的ARP解析请求。这种情况下ether_addr可以设置成auto。以 后会检查本机所有接口,如果其中一个接口所配置IP被发现与ARP解析 请求的目标IP位于同一子网,则响应以该接口的MAC地址。 -S hostname ether_addr 与-s非常类似,区别在于如果已经存在一个对应该hostname的ARP表项, 则系统会先删除以前的表项,再添加。 -f 读取指定文件中的内容创建多个ARP表项,文件中每行格式如下 hostname ether_addr [temp] [pub] 注意,仅仅-f不意味着永久静态ARP表项,需要和-s、-S配合使用。 参看 inet(3) arp(4) ifconfig(8) 历史 arp命令源自4.3 BSD -------------------------------------------------------------------------- A: scz 下面举例说明FreeBSD 4.3-RELEASE中arp的使用 1) 响应针对一个不存在的IP地址192.168.0.4的ARP解析请求 arp -S 192.168.0.4 auto pub 2) 清空arp cache arp -da 3) 不带反向域名解析查看arp cache arp -na 4) 删除一个published表项 arp -d 192.168.0.4 proxy 但是在测试过程中没有发现必须指定proxy关键字,不是很懂。 5) 设置静态ARP表项 arp -s 192.168.8.90 0:0:0:11:11:11 arp -n 192.168.8.90 <-- 查看指定的ARP表项 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 | grep ifspeed 某些x86网卡驱动支持,某些不支持,这个办法同样适合于SPARC网卡驱动,虽然后者 可以直接使用ndd(1M)。 10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed A: 小四 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 在高版本的Linux系统中net-tools包中有一个mii-tool命令,可以用于检查这些数据, 而不是溶合在ifconfig的输出中。 Q: Linux系统除了mii-tool命令还有其它办法判断链路层是否接通吗。 A: wheelz@www.linuxforum.net 2005-08-28 12:56 ioctl( , SIOCGIFFLAGS, ) 判断IFF_RUNNING是否置位。 以前一直以为Linux下只能通过mii-tool查看链路层是否接通,看了wheelz的回答才 知道"ifconfig eth0 | grep RUNNING"即可判断链路层是否接通。 -------------------------------------------------------------------------- /* * For Linux * gcc -Wall -pipe -O3 -s -o linkdetect linkdetect.c */ #include #include #include #include #include #include #include #include #include #include /* * 不包括结尾的0。 * * 参/usr/include/linux/if.h * * #define IFNAMSIZ 16 */ #define MAXDEVICENAMELEN 15 #define DEFAULT_DEVICE "eth0" int main ( int argc, char * argv[] ) { int s = -1, ret = EXIT_FAILURE; struct ifreq ifr; char *device = DEFAULT_DEVICE; if ( argc >= 2 ) { if ( strlen( argv[1] ) > MAXDEVICENAMELEN ) { fprintf( stderr, "Checking your \n" ); goto main_exit; } device = argv[1]; } memset( &ifr, 0, sizeof( ifr ) ); strcpy( ifr.ifr_name, device ); if ( ( s = socket( PF_INET, SOCK_DGRAM, IPPROTO_IP ) ) < 0 ) { perror( "socket error" ); goto main_exit; } if ( ioctl( s, SIOCGIFFLAGS, &ifr ) ) { perror( "ioctl SIOCGIFFLAGS error" ); goto main_exit; } if ( ifr.ifr_flags & IFF_RUNNING ) { printf( "Link Up(RUNNING)\n" ); } else { printf( "Link Down\n" ); } ret = EXIT_SUCCESS; main_exit: if ( -1 != s ) { close( s ); s = -1; } return( ret ); } /* end of main */ -------------------------------------------------------------------------- $ ./linkdetect Link Up(RUNNING) $ ./linkdetect eth1 Link Down $ ./linkdetect eth2 Link Down $ ./linkdetect lo Link Up(RUNNING) 10.13 x86/FreeBSD 4.3-RELEASE下LINK_ADDR(3)手册页 A: x86/FreeBSD 4.3-RELEASE下LINK_ADDR(3)手册页 -------------------------------------------------------------------------- LINK_ADDR(3) FreeBSD库函数手册 LINK_ADDR(3) 名字 link_addr、link_ntoa - 链路层地址函数 库 标准C库(libc、-lc) 摘要 #include #include #include void link_addr ( const char *addr, struct sockaddr_dl *sdl ); char * link_ntoa ( const struct sockaddr_dl *sdl ); 描述 link_addr()的第一形参指定一个字符串,该字符串描述了链路层地址,在第二 形参sdl中返回适合系统调用使用的二进制数据。主调者自己确保sdl指向的空 间确实存在。 注:关于这里,参看下面演示程序的另外一个重要说明,标准的 struct sockaddr_dl不足以存放link_addr()的返回值。 注:根据src/lib/libc/net/linkaddr.c文件对此man手册做些修正 1) 调用link_addr()之前务必有效初始化sdl->sdl_len成员 2) 标准的struct sockaddr_dl可能不足以存放link_addr()的返回值,必 须由主调者自己确保sdl->sdl_data数据区足够大 link_ntoa()根据一个链路层地址结构返回一个相应的ASCIIZ串,包括网络接口 名和链路层地址。这个函数在变动中,务必小心使用。 对于link_addr(),形参addr可能包含一个可选的网络接口名,比如fxp0,也就 是ifconfig(8)使用的网络接口名。 "le0:8.0.9.13.d.30"表示第一个以太网接口链路层地址。 注:事实上根据我的测试,这些都是合法输入 :0:0:0:11:11:11 .0.0.0.11.11.11 nonexist:0,0.0.11.11,11 可见这个函数实现有多混乱。更搞笑的是,有效数字前面必须有一个分隔 符,比如冒号、点号、逗号什么的。 由于存在addr2ascii(3)接口,并不赞成直接使用这两个函数。然而,如果一个 程序对可移植性要求较高,就不能使用addr2ascii(3)接口,这是一个未被广泛 实现的接口。 返回值 link_ntoa()总是返回一个ASCIIZ串,以NULL结尾。link_addr()没有返回值。 参看 addr2ascii(3) 历史 4.3 BSD-Reno开始出现link_addr()和link_ntoa()函数 BUGS link_ntoa()的返回值在static内存中,不可重入。 link_addr()第一形参支持的格式相当含糊不清。 如果sdl->sdl_len为零,link_ntoa()所返回的字符串前没有前导冒号,这样的 字符串不会被link_addr()正确解析。 注:man手册有误,这里应该是如果sdl->sdl_nlen为零,link_ntoa()所返回的 字符串前没有前导冒号,这样的字符串不会被link_addr()正确解析。 -------------------------------------------------------------------------- D: 小四 2002-02-03 17:22 下述讨论未做修正,参看2002-02-05 10:33 backend所做进一步讨论进行理解 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o linkaddr_test linkaddr_test.c */ #include #include #include #include #include #include #include int main ( int argc, char * argv[] ) { /* * 如果直接使用struct sockaddr_dl,link_addr()调用后会造成缓冲区溢出, * 换句话说,标准的struct sockaddr_dl不足以存放link_addr()的返回数据。 * 目前尚不清楚这是gcc、FreeBSD、库三者中谁的问题。下面那个Bus error * 也很蹊跷。 */ unsigned char data[1024]; struct sockaddr_dl *sdl = ( struct sockaddr_dl * )&data; unsigned char *mac = NULL; int i; if ( argc != 2 ) { fprintf( stderr, "Usage: %s \n", argv[0] ); return( EXIT_FAILURE ); } /* * 很奇怪,下面这条语句导致Bus error,x86/FreeBSD 4.3-RELEASE平台 * gcc version 2.95.3 [FreeBSD] 20010315 (release) * * memset( data, 0, sizeof( data ) ); */ link_addr( argv[1], sdl ); for ( i = 0; i < sdl->sdl_nlen; i++ ) { fprintf( stderr, "%c", sdl->sdl_data[i] ); } if ( sdl->sdl_nlen != 0 ) { fprintf( stderr, ":" ); } mac = ( unsigned char * )LLADDR( sdl ); for ( i = 0; i < sdl->sdl_alen; i++ ) { fprintf( stderr, "%c%02x", i ? ':' : '\0', mac[i] ); mac[i] = i; } fprintf( stderr, "\n" ); fprintf( stderr, "%s\n", link_ntoa( sdl ) ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- $ ./linkaddr_test nonexist:0,0.0.11.11,11 nonexist:00:00:00:11:11:11 nonexist:0.1.2.3.4.5 $ ./linkaddr_test :0,0.0.11.11,11 00:00:00:11:11:11 <-- sdl->sdl_nlen为0,sdl->sdl_alen为6 0.1.2.3.4.5 <-- 注意此时link_ntoa()的输出 $ ./linkaddr_test 0,0.0.11.11,11 0:00:00:11:11:11 <-- sdl->sdl_nlen为1,sdl->sdl_alen为5 0:0.1.2.3.4 $ 这解释了"route add -host 192.168.5.8 -link :0:0:0:11:11:11 -iface"命令中为 何不能丢掉MAC地址前导冒号。 D: backend 2002-02-05 10:33 backend对前述问题做了跟踪分析,已经找到原因。 参看src/lib/libc/net/linkaddr.c文件 -------------------------------------------------------------------------- /* * States */ #define NAMING 0 #define GOTONE 1 #define GOTTWO 2 #define RESET 3 /* * Inputs */ #define DIGIT (4*0) #define END (4*1) #define DELIM (4*2) #define LETTER (4*3) void link_addr ( register const char *addr, register struct sockaddr_dl *sdl ) { register char *cp = sdl->sdl_data; char *cplim = sdl->sdl_len + ( char * )sdl; register int byte = 0 register int state = NAMING, new; /* * 这里一开始就利用了sdl->sdl_len成员,意味着为了正确使用link_addr()函 * 数,必须有效初始化sdl->sdl_len成员,而我们可爱的man手册里居然不提这 * 点。如果sdl->sdl_len成员为零,bzero()的第二形参是-1。bzero的函数原 * 型是 * * void bzero ( void *b, size_t len ); * * 因为size_t是无符号类型,-1也就是0xffffffff,导致在清零过程中越界, * 出现x86上并不常见的Bus error. */ bzero( ( char * )&sdl->sdl_family, sdl->sdl_len - 1 ); sdl->sdl_family = AF_LINK; do { state &= ~LETTER; if ( ( *addr >= '0' ) && ( *addr <= '9' ) ) { new = *addr - '0'; } else if ( ( *addr >= 'a' ) && ( *addr <= 'f' ) ) { new = *addr - 'a' + 10; } else if ( ( *addr >= 'A' ) && ( *addr <= 'F' ) ) { new = *addr - 'A' + 10; } else if ( *addr == 0 ) { state |= END; } else if ( state == NAMING && ( ( ( *addr >= 'A' ) && ( *addr <= 'Z' ) ) || ( ( *addr >= 'a' ) && ( *addr <= 'z' ) ) ) ) { state |= LETTER; } else { /* * 一切能进入此处的字符都可当做分隔符 */ state |= DELIM; } addr++; switch ( state ) { case NAMING | DIGIT: case NAMING | LETTER: *cp++ = addr[-1]; continue; case NAMING | DELIM: /* * 从这里也可以看出为什么route命令指定MAC地址时需要前导分隔符 */ state = RESET; sdl->sdl_nlen = cp - sdl->sdl_data; continue; case GOTTWO | DIGIT: *cp++ = byte; /* * FALLTHROUGH */ case RESET | DIGIT: state = GOTONE; byte = new; continue; case GOTONE | DIGIT: state = GOTTWO; byte = new + ( byte << 4 ); continue; default: state = RESET; *cp++ = byte; byte = 0; continue; case GOTONE | END: case GOTTWO | END: *cp++ = byte; /* * FALLTHROUGH */ case RESET | END: break; } break; } while ( cp < cplim ); /* * 如果sdl->sdl_len为0,则do while循环只进行了一次就结束了,必将导致 * sdl->sdl_alen为负值(一个很大的正值),而下面那个if判断必将失败,然后 * sdl->sdl_len继续保持为0 */ sdl->sdl_alen = cp - LLADDR( sdl ); new = cp - ( char * )sdl; if ( new > sizeof( *sdl ) ) { /* * 这里对sdl->sdl_len成员重新做了赋值。整个link_addr()的实现比较混 * 乱,很容易出现缓冲区溢出,务必在调用link_addr()之前确认第一形参 * 和第二形参的关系。 */ sdl->sdl_len = new; } return; } /* end of link_addr */ static char hexlist[] = "0123456789abcdef"; char * link_ntoa ( register const struct sockaddr_dl *sdl ) { /* * link_ntoa()通过这个static变量返回数据,是不可重入的 */ static char obuf[64]; register char *out = obuf; register int i; register u_char *in = ( u_char * )LLADDR( sdl ); u_char *inlim = in + sdl->sdl_alen; int firsttime = 1; /* * man手册有误,这里应该是如果sdl->sdl_nlen为零,link_ntoa()所返回的字 * 符串前没有前导冒号,这样的字符串不会被link_addr()正确解析。 */ if ( sdl->sdl_nlen ) { bcopy( sdl->sdl_data, obuf, sdl->sdl_nlen ); out += sdl->sdl_nlen; if ( sdl->sdl_alen ) { *out++ = ':'; } } while ( in < inlim ) { if ( firsttime ) { firsttime = 0; } else { *out++ = '.'; } i = *in++; if ( i > 0xf ) { out[1] = hexlist[ i & 0xf ]; i >>= 4; /* * i来自u_char,所以这里不需要i & 0xf了 */ out[0] = hexlist[i]; out += 2; } else { *out++ = hexlist[i]; } } *out = 0; return( obuf ); } /* end of link_ntoa */ -------------------------------------------------------------------------- 在linkaddr_test.c中导致Bus error的 memset( data, 0, sizeof( data ) ); 并不是真正问题所在。如果不做这个操作,data[]在stack中分配,在我前面的测试 中,data[]中前部的数据非0,一般来说stack中的数据很乱,初始随机数据是零的机 率并不很大,也就意味着sdl->sdl_len成员非零,进入link_addr()后的bzero()操作 尽管可能越界,不是很夸张的话,也不至于立刻导致Bus error。 如果做了memset()操作,sdl->sdl_len成员为零,必将立即导致Bus error。 当时在gdb中看到问题出在bzero()上,可我以为是说memset()这句,其间还担心是兼 容性问题,换成bzero(),Bus error依旧,未曾想到是link_addr()中的bzero()出错。 重新来检验一回,将memset()增加回来,重新编译 $ gcc -Wall -pipe -g -o linkaddr_test linkaddr_test.c $ ./linkaddr_test .0.0.0.1.1.1 Bus error (core dumped) <-- 这里生成./linkadd_test.core文件 $ gdb ./linkaddr_test ./linkaddr_test.core #0 0x280d38e9 in bzero () from /usr/lib/libc.so.4 (gdb) bt #0 0x280d38e9 in bzero () from /usr/lib/libc.so.4 #1 0x280e5ea8 in .curbrk () from /usr/lib/libc.so.4 #2 0x8048625 in main (argc=671761162, argv=0xbfbff7f5) at linkaddr_test.c:35 (gdb) list 34,36 这里指明是源文件35行出错^^^^^^^^^^^^^^^^^^ 34 memset( data, 0, sizeof( data ) ); 35 link_addr( argv[1], sdl ); 36 for ( i = 0; i < sdl->sdl_nlen; i++ ) (gdb) q $ 看来偷懒还是不成啊,35行正是link_addr()函数调用。修改linkaddr_test.c如下 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o linkaddr_test_1 linkaddr_test_1.c */ #include #include #include #include #include #include #include int main ( int argc, char * argv[] ) { /* * 如果直接使用struct sockaddr_dl,link_addr()调用后会造成缓冲区溢出, * 换句话说,标准的struct sockaddr_dl不足以存放link_addr()的返回数据。 * 这是link_addr()实现上的问题 */ unsigned char data[1024]; struct sockaddr_dl *sdl = ( struct sockaddr_dl * )&data; unsigned char *mac = NULL; int i; if ( argc != 2 ) { fprintf( stderr, "Usage: %s \n", argv[0] ); return( EXIT_FAILURE ); } memset( data, 0, sizeof( data ) ); /* * 这个值有可能在link_addr()返回后被修正 */ sdl->sdl_len = sizeof( struct sockaddr_dl ); link_addr( argv[1], sdl ); for ( i = 0; i < sdl->sdl_nlen; i++ ) { fprintf( stderr, "%c", sdl->sdl_data[i] ); } if ( sdl->sdl_nlen != 0 ) { fprintf( stderr, ":" ); } mac = ( unsigned char * )LLADDR( sdl ); for ( i = 0; i < sdl->sdl_alen; i++ ) { fprintf( stderr, "%c%02x", i ? ':' : '\0', mac[i] ); mac[i] = i; } fprintf( stderr, "\n" ); fprintf( stderr, "%s\n", link_ntoa( sdl ) ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 在x86/FreeBSD 4.3-RELEASE上验正无误 10.14 traceroute是怎么实现的 Q: traceroute是怎么实现的 A: 小四 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 A: 小四 1) 传统的traceroute操作是发送UDP报文,等待ICMP端口不可达报文、ICMP超时报文。 2) 把1)中的UDP报文换成TCP报文,没有什么不可以,只不过最后等待的不是ICMP端 口不可达报文,而是RST报文。1)和2)多是Unix系统traceroute实现,用NetXray 自己抓包观察。 3) 发送Icmp Echo Request,等待ICMP超时报文和Icmp Echo Reply。这是Windows系 统的tracert实现。 4) 记录路由选项和Icmp Echo Request报文配合获取到达目标主机所经过的路由,也 可以和TCP、UDP报文配合。记录路由选项具体格式可以参看介绍TCP/IP协议的书 籍或RFC 791。IP选项最多40字节,这个限制使得记录路由选项只能包含9跳路由, 请对照记录路由选项具体格式理解为什么只能包含9跳路由。这多是Unix系统的 traceroute实现。 D: 小四 2002-05-17 14:09 一般traceroute等待回应ICMP包5秒,发送三个探测分组。对于使用TCP的情况,应该 等待RST报文,而不是ICMP报文。 1) 在WinXP上 ping -n 1 -i 255 tracert -d -h 30 -w 5000 2) 在SPARC/Solaris 8上的traceroute -d 设置SO_DEBUG套接字选项 -F 设置"不可分片"位(DF),仅IPv4有效 -f first_hop 设置初始ttl,而不是缺省的1。此时traceroute略过那些小于first_hop 的路由 -I 使用ICMP Echo探测分组,而不是缺省的UDP探测分组 -l 显示每个返回包的TTL(hop limit) -m max_hop 设置探测分组所用最大TTL(hop limit),缺省是30 hops。TCP连接缺省也 用30 hops。 -n 不做反向域名解析 -p port 设置探测分组起始UDP端口,缺省是33434。traceroute希望对端没有侦听 相应的UDP端口 (base+(nhops-1)*nqueries) <-> (base+(nhops*nqueries)-1) nhops和nqueries从1开始递增。nhops定义源与目的之间的跳数。 这样才能收到ICMP PORT_UNREACHABLE报文。 -q nqueries 设置每组探测报文个数,缺省是3。 -w waittime 设置等待ICMP报文时间(单位是秒),缺省是5秒。 3) 在x86/FreeBSD 4.5-RELEASE上traceroute -S 显示每跳丢失了多少探测分组 -M 设置探测分组起始TTL,缺省为1。 -m 设置探测分组最大TTL,缺省值由net.inet.ip.ttl决定,比如64。TCP连 接用同样的缺省设置。 -n 不做反向域名解析 -P 指定探测分组所用协议,目前支持UDP、TCP和GRE。注意,至少到4.5为 止,FreeBSD的traceroute不支持ICMP探测。 -p 协议相关的。对于UDP和TCP,这里指定探测分组起始端口,缺省是33434。 traceroute期望在base到base + nhops - 1范围上无端口被侦听。 -q 设置每组探测报文个数,缺省是3。 -v 冗余输出 -w 设置等待ICMP报文时间(单位是秒),缺省是5秒。 4) 在x86/Linux Kernel 2.4.7-10上 -f 设置探测分组起始TTL,缺省为1。 -F 设置"不可分片"位(DF) -d 设置SO_DEBUG套接字选项 -I 使用ICMP ECHO而不是UDP报文做探测分组 -m 设置探测分组所用最大TTL(hop limit),缺省是30 hops。TCP连接缺省 也用30 hops。 -n 不做反向域名解析 -p 设置探测分组起始UDP端口,缺省是33434。traceroute期望在base到 base + nhops - 1范围上无端口被侦听。 -v 冗余输出 -w 设置等待ICMP报文时间(单位是秒),缺省是5秒。 10.15 SPARC/Solaris 8 snoop(1M)手册页 A: 小四 2002-03-27 17:06 -------------------------------------------------------------------------- 维护命令 snoop(1M) 名字 snoop - 捕获、检查网络报文 摘要 snoop [ -aqrCDNPSvV ] [ -t [ r | a | d ] ] [ -c maxcount ] [ -d device ] [ -i filename ] [ -n filename ] [ -o filename ] [ -p first [ , last ] ] [ -s snaplen ] [ -x offset [ , length ] ] [ expression ] 描述 snoop使用了pfmod、bufmod流模块。modinfo可以看到这两个流模块。 在SUNWcsu包中 选项 -a Listen to packets on /dev/audio (warning: can be noisy) -C 显示包过滤伪代码,不实际开始抓包。 -D 显示在抓包过程中被丢弃的报文数目 -N 根据-o产生的文件内容生成一个IP地址到名字的映射文件。这个选项必须与-i一 起使用。生成的文件名为filename.names。 -P 不进入混杂模式,此时抓取广播包、组播包,以及对应自身的单播包。注意此时 抓取不了本机发送出去的报文。 -q 当使用了-o选项时,如果指定-q,则不显示当前所捕获的包个数。这有助于提高 包的捕获效率。 -r 不做反向域名解析,这使得snoop时不会因反向域名解析产生其它网络流量。如 果指定了-n,而且在指定映射文件中找到相应条目,相应名字还是会被使用。 -S 显示整个物理帧的字节数 -v 冗余模式 -V 冗余摘要模式。比-v要稍微少点信息。如果不指定,则只显示最高层协议的摘要 信息,指定后显示各层协议的摘要信息。比如,对于一个NFS报文,将显示ETHER、 IP、UDP、RPC、NFS的摘要信息。这种模式的输出很容易用grep析取相应协议层 的摘要信息,比如 # snoop -i rpc.cap -V | grep RPC -t [ r | a | d ] 时间戳精确在百万分之四秒内 d(delta),缺省设置,与上一包接收时刻的间隔。a(absolute),显示绝对时钟。 r(relative)显示相对第一个包的相对时钟,以秒为单位,可与-p相配合。 -c maxcount 只抓这么多包,而不是缺省的持续抓包 -d device device诸如hme0、le0等等。指定抓包的网络接口。netstat -i可以看到本机所 有网络接口。注意,这里不支持回送接口。如果不指定,snoop自动选择它所能 找到的第一个非回送接口。 -i filename 显示保存在filename中的原始包,而不是从网络接口抓包后动态显示。如果存在 filename.names文件,将自动以-n filename.names方式调入。 -n filename 指定IP地址到名字的映射文件。文件格式同/etc/hosts。 -o filename 将所捕获的原始包存入指定文件,而不是向屏幕输出,这样有助于提高抓包效率。 此时屏幕上只显示已经写入文件的报文个数。如果你只是希望统计报文个数,而 不想写入文件,可以指定-o /dev/null -p first [ , last ] 与-i相配合,指定被显示包的范围。从1开始计。 -s snaplen 以snaplen为报文大小的上限,而通常是整个包被捕获。只想获取某些头部信息 时,就可以使用这个选项了。这个上限是在内核中实现的,利用了bufmod模块, 有助于提高抓包效率、节省磁盘空间。比如只需要IP头信息(假设无选项)时,可 指定-s 34。同理-s 42抓UDP头,-s 54抓TCP头,-s 80抓RPC头,-s 120抓NFS头。 snaplen是从链路层首部算起的。 -x offset [ , length ] dump所抓取的报文。偏移0表示物理帧首部。如果没有指定length,则从offset 开始的所有字节被dump出来。 表达式 snoop既使用了pfmod,也有自己的过滤机制(用户空间的)。当从网络接口抓包时, 使用内核中的pfmod,这是bufmod的上行流。报文从网络接口上来后先到达 bufmod,然后到达pfmod,然后到达snoop的缓冲区。pfmod实现有限,可能某些 过滤表达式无法由pfmod单独处理,此时snoop先试图分解这种过滤规则,多次使 用pfmod,最后实在无法利用pfmod的将使用snoop自己的包过滤器。可用-C选项 观察过滤表达式所对应的伪代码。如果使用-i选项,将只使用snoop自己的包过 滤器。 过滤表达式支持and、or、not、圆括号。由于圆括号可能为shell所解释,必要 时应该使用引号将表达式引起来。 host hostname 限定源或目标地址。这里可以指定主机名。在不引起冲突的时候,host关键字可 以省略,比如pinky and dinky限定只抓这两者之间的通信报文。 可在host前指定地址类型,目前可用的有inet、inet6、ether、none。指定none 等价于"inet host hostname or inet6 host hostname"。如果不指定ether,默 认是IP地址。 可以指定如下类型的地址, 1) 192.168.5.8 2) 8:0:20:f:b1:51 当MAC地址以字母开头时会被解释成主机名,为避免这个问题,可在前面写一个0, 比如"0aa:0:45:23:52:44" from or src 对应源地址、源端口、RPC响应(RPC Reply) to or dst 对应目标地址、目标端口、RPC请求(RPC Call) ethertype number 等价于"ether[12:2] = number",比如0x0800、0x0806 ip, ip6, arp, rarp 指定物理帧负载所使用的协议 broadcast 等价于"ether[2:4] = 0xffffffff" multicast 等价于"ether[0] & 1 = 1" apple 限定Apple Ethertalk Packet,等价于 "ethertype 0x809b or ethertype 0x803f" decnet 限定DECNET Packet greater length 限定报文大小超过length less length 限定报文大小小于length udp, tcp, icmp, icmp6, ah, esp 限定IP报文上层负载所使用的协议 net net 限定子网号(仅IP协议),可以与from、to一起使用 port port 限定端口。如果使用名字,将采用/etc/services。 rpc prog [ , vers [ , proc ] ] 限定远程程序号、远程程序版本号、远程过程号。prog可以采用/etc/rpc。 "rpc nfs,2,0"限定了NFS null procedure。 gateway host 限定MAC地址是host对应的,但IP地址不是host对应的。等价于 "ether host host and not host host"。实际限定了那些进出网关的报文。 nofrag 限定非碎片包或是第一个碎片包,等价于"ip[6:2] & 0x1fff = 0" expr relop expr relop即关系表达式(>, <, >=, <=, =, !=),expr中可以出现(+, -, *, &, |, ^, %)。算术运算符优于关系运算符。可以使用圆括号。 base[offset[:size]] base可以是(ether, ip, udp, tcp, icmp),size指定长度,如果未指定,缺省 为1。此外size还可取2、4。 ether[0] & 1 = 1 等价于 multicast ether[2:4] = 0xffffffff 等价于 broadcast ip[ip[0] & 0xf * 4 : 2] = 2049 等价于 udp[0:2] = 2049 ip[6:2] & 0x1fff = 0 等价于 nofrag ip[0] & 0xf > 5 限定带IP选项的报文。 udp and ip[6:2]&0x1fff = 0 and udp[6:2] != 0 限定所有做了UDP检验和的报文 length 获取报文长度 length > 60 等价于 greater 60 ether[length - 1] 获取报文的最后一个字节 and 逻辑与,"dinky pinky" 等价于 "dinky AND pinky" or or , 逻辑或,"dinky,pinky"等价于"dinky OR pinky" not or ! 逻辑非。 slp 限定SLP Packet 例子 # snoop -o cap funky pinky # snoop -i cap -t r | more # snoop -i pkts -p99,108 # snoop -i pkts -v -p101 # snoop -i pkts rpc nfs and sunroof and boutique # snoop -i pkts -o pkts.nfs rpc nfs sunroof boutique snoop还可以限定ip-in-ip # snoop ip-in-ip # snoop -V ip-in-ip 为了建立高效的过滤机制,下列关键字应该尽量出现在表达式的尾部 greater, less, port, rpc, nofrag, relop 逻辑或使得不易利用内核过滤机制,此时考虑用圆括号 # snoop funky and pinky and (tcp or udp) and port 80 退出状态 0 成功 1 发生错误 文件 /dev/audio /dev/null /etc/hosts /etc/rpc /etc/services 参看 netstat(1M), hosts(4), rpc(4), services(4), attributes(5), audio(7I), bufmod(7M), dlpi(7P), le(7D), pfmod(7M), tun(7M) 警告 实时包解析的消耗很大,此时丢包率会较高。考虑-o、-i选项的使用 冗余模式会增大系统负载 snoop不会重组IP碎片,如果高层协议被分散在几个IP碎片中,只有第一个IP碎 片中的高层协议会被解析。 实时解析时应该指定-r选项取消反向域名解析 如果snaplen指定太小,可能导致缺少解析高层协议所必需的信息。对在10Mb/s 以太网上采用UDP协议的NFSv2,snaplen不应低于150字节。对在100Mb/s以太网 上采用TCP协议的NFSv3,snaplen不应低于250字节或更多。 为了完整解析一个RPC Reply,snoop需要相应的RPC Request,如果在-o输出文 件中或-p指定范围中,一个RPC Reply缺少相应的RPC Request,此时只显示RPC Reply的头部信息。 -------------------------------------------------------------------------- 10.16 x86/FreeBSD TCPDUMP(1)手册页 A: 小四 2002-03-28 09:53 -------------------------------------------------------------------------- TCPDUMP(1) TCPDUMP(1) 名字 tcpdump - 捕获并显示网络报文 摘要 tcpdump [ -adeflnNOpqRStvxX ] [ -c count ] [ -F file ] [ -i interface ] [ -m module ] [ -r file ] [ -s snaplen ] [ -T type ] [ -w file ] [ expression ] 描述 在SunOS上使用nit或bpf,为了使用tcpdump,必须能够读访问/dev/nit或 /dev/bpf*。在Solaris上使用DLPI,必须能够读访问网络伪设备文件,比如 /dev/le、/dev/hme。在HP-UX上使用DLPI,必须是root或拥有root权限(SUID)。 在IRIX上使用snoop,必须是root或拥有root权限(SUID)。在Linux上,使用 SOCK_PACKET,必须是root或拥有root权限(SUID)。在Ultrix和Digital UNIX上, 一旦超级用户使用pfconfig(8)允许混杂模式操作,任意用户都可运行tcpdump。 在*BSD上,必须能够读访问/dev/bpf*。 选项 -a 试图将网络地址、广播地址转换成名字 -c count 只抓这么多包,抓够便退出,而不是缺省的持续抓包 -d 不实际抓包,显示包过滤表达式的伪代码,最常用的观察方式 -dd 类似-d,以C程序片段的风格显示包过滤表达式的伪代码 -ddd 类似-d,另一种显示伪代码的方式 -e 显示数据链路层头部信息(14个字节) -f 对于'foreign'地址,以数字形式显示,而非符号形式。对于使用Sun ypserver 的环境,应该使用-f,否则为了转换非本地IP地址,消耗很大。 -F 用指定文件中的内容做过滤表达式,忽略命令行上的过滤表达式 -i 指定网络接口名,如果没有指定,tcpdump搜索所有网络接口,使用第一个UP了 的非回送接口。这个意思是说,不能指定一个DOWN了的接口? FreeBSD上tcpdump可以指定-i lo0,这与Solaris上不同。 -l 指定stdout进入行缓冲模式,比如 tcpdump -l | tee dat tcpdump -l > dat & tail -f dat -n 取消缺省的反向域名解析,包括不对端口号做反向名字解析 -N 不显示FQDN(全称域名)的域部分,而只显示主机部分,比如显示"nic",而非 "nic.ddn.mil" -m 指定一个SMI MIB文件,分析SNMP协议时使用。 -O 不要进行包过滤代码优化。仅当怀疑优化存在bug时才指定。 -p 抓包时网络接口不进入混杂模式。注意,接口可能因为其他原因进入混杂模式。 -p不等价于ether host {local-hw-addr}或者ether broadcast -q 快速(安静)输出,只显示少量协议信息,输出行较短 -r file 显示保存在file中的原始包,而不是从网络接口抓包后动态显示。对应-w选项。 file为"-"时表示标准输入。 -s snaplen 指定snaplen,缺省是68(对于SunOS NIT是96)。68字节对于只看头部信息的抓包 差不多够了,但对DNS、NFS报文来说,可能就不够了。因snaplen而被截断的报 文在显示时有提示。注意,一个大的snaplen会增大负载,加大丢包率。 snaplen是从链路层首部算起的。 -T type 强制以指定类型解析被捕获的报文,type可以为 rpc (Remote Procedure Call) rtp (Real-Time Applications protocol) rtcp (Real-Time Applications control protocol) snmp (Simple Network Management Protocol) vat (Visual Audio Tool) wb (distributed White Board) -R 按老的标准(RFC1825 to RFC1829)解析ESP/AH Packets -S 显示绝对TCP序列号,而非缺省的相对TCP序列号 -t 不显示时间戳 -tt 显示未格式化过的时间戳 -v 轻度冗余输出模式 -vv 中度冗余输出模式 -vvv 重度冗余输出模式 -w file 将所捕获的原始包存入指定文件,而不是向屏幕输出,这样有助于提高抓包效率。 此时不会做报文解析。该选项与-r配合使用。如果file指定"-",表示标准输出。 -x 以16进制dump所捕获的报文(不包括数据链路层14个字节)。整个包如果比 snaplen小,则dump整个包,反之受snaplen限制。 -X 16进制dump的同时显示ascii表示。就是说-x -X同时指定后,报文以hex/ascii 方式dump出来。即使没有指定-x而只指定-X,某些报文的某些部分也有可能以 hex/ascii方式dump出来。 过滤表达式 一般有这种形式, 目前有三类 type 包括host、net、port,比如"host foo"、"net 128.3"、"port 20"。如果没有 指定,默认是host dir 包括src、dst、src or dst、src and dst,比如"src foo"、"dst net 128.3"、 "src or dst port ftp-data"。如果未指定,默认是"src or dst" 对于slip这样的链路层协议,还可以使用inbound、outbound proto 包括ether, fddi, ip, ip6, arp, rarp, decnet, lat, sca, moprc, mopdl, iso, esis, isis, icmp, icmp6, tcp, udp等等。比如"ether src foo"、"arp net 128.3"、"tcp port 21"。如果未指定,则捕获所有协议。比如"src foo"意 味着"(ip or arp or rarp) src foo","net bar"意味着 "(ip or arp or rarp) net bar","port 53"意味着"(tcp or udp) port 53" 对于tcpdump,fddi实际是ether的别名,对于解析器来说,二者是一样的。 and or not 表达式支持这三个关键字,比如 "host foo and not port ftp and not port ftp-data"。 可以省略连在一起的同一qualifier,比如 "tcp dst port ftp or ftp-data or domain"与 "tcp dst port ftp or tcp dst port ftp-data or tcp dst port domain" 等价。 如下 dst host 限定目标地址(IPv4/IPv6),支持数字形式以及名字 src host 限定源地址(IPv4/IPv6),支持数字形式以及名字 host 限定地址(源或目标,IPv4/IPv6) 这三种表达式前可以指定这些关键字(ip, arp, rarp, ip6),比如 "ip host ",等价于 "ether proto \ip and host "。 如果以名字形式指定,而又对应多个IP地址,则每个IP地址都会被拿来进 行匹配。 ether dst 限定目标MAC地址。可以使用/etc/ethers,参看ethers(3N)。 ether src 限定源MAC地址。可以使用/etc/ethers,参看ethers(3N)。 ether host 限定MAC地址(源或目标)。可以使用/etc/ethers,参看ethers(3N)。 gateway 限定MAC地址是host对应的,但IP地址不是host对应的。实际限定了那些进出网 关的报文。必须是一个名字,要求同时在/etc/hosts和/etc/ethers文件 中。等价于"ether host and not host "。这种用法不适用于IPv6。 dst net 限定目标网络地址(IPv4/IPv6),支持数字形式以及名字。参看/etc/networks、 networks(4)。 src net 限定源网络地址(IPv4/IPv6),支持数字形式以及名字。参看/etc/networks、 networks(4)。 net 限定网络地址(源或目标,IPv4/IPv6),支持数字形式以及名字。参看 /etc/networks、networks(4)。 net mask IP地址与指定掩码相与后匹配指定网络地址。可以配合src、dst关键字。不适用 于IPv6。 net / 同上,另一种表达形式 dst port 限定目标端口,适用于ip/tcp, ip/udp, ip6/tcp, ip6/udp。支持数字形式以及 名字,参看/etc/services、tcp(4P)、udp(4P)。如果指定了名字,端口号和协 议同时被检查。如果指定了一个暧昧的端口号或名字,则只检查端口号,不检查 协议,比如"dst port 513"将匹配tcp/rlogin和udp/rwho。 src port 类似上面,限定源端口 port 类似上面,限定端口(源或目标) 这三种表达式前可以指定这些关键字(tcp、udp),比如"tcp src port " less 等价于"len <= length" greater length 等价于"len >= length" ip proto 限定IP负载所使用的上层协议,参看ip(4P)。支持数字形式或名字( icmp, igrp, udp, nd, tcp)。由于tcp、udp、icmp本身是tcpdump的关键字,指 定时前面应该有一个\,对于csh则是\\ ip6 proto 类似上面,区别在于一个是IPv4,一个是IPv6 ether broadcast 限定链路层广播包。ether关键字是可选的。 ip broadcast 限定IP层广播包,匹配全零、全一以及定向广播包(掩码介入)。 ether multicast 限定链路层组播包。ether关键字是可选的。等价于"ether[0] & 1 != 0" ip multicast 限定IP层组播包 ip6 multicast 类似上面,适用于IPv6 ether proto 限定物理帧负载所使用的上层协议。支持数字形式或名字(ip, ip6, arp, rarp)。由于这些名字本身是tcpdump的关键字,指定时前面应该有一个\, 对于csh则是\\ decnet src 限定DECNET源地址 decnet dst 限定DECNET目标地址 decnet host host 限定DECNET地址(源或目标) ip, ip6, arp, rarp, decnet, iso, lat, moprc, mopdl 等价于相应的"ether proto " tcp, udp, icmp 等价于"ip proto or ip6 proto " esis, isis 等价于"iso proto " expr relop expr relop即关系表达式(>, <, >=, <=, =, !=),expr中可以出现(+, -, *, &, |, ^, %)。算术运算符优于关系运算符。可以使用圆括号。 proto [ offset : size ] proto可以是(ether, fddi, ip, arp, rarp, tcp, udp, icmp, ip6)。当指定 tcp、udp等上层协议时,只适用于IPv4,不适用于IPv6。offset可以用表达式指 定。size是可选的,有效值为1、2、4,缺省是1。 "ether[0] & 1 != 0"捕获所有组播包 "ip[0] & 0xf != 5"捕获所有携带IP选项的IP报文 "ip[6:2] & 0x1fff = 0"限定非碎片包或是第一个碎片包 如果高层协议被分散在几个IP碎片中,只有第一个IP碎片中的高层协议会被解析。 len 获取报文长度 圆括号 由于圆括号可能为shell所解释,必要时应该使用引号将表达式引起来或使用\ !或not 逻辑非 &&或and 逻辑与 ||或or 逻辑或 与Solaris的snoop不同(与常规也不同),and和or的优先级是相同的,从左到右 结合。注意,现在"foo bar"不再等价于"foo and bar"了。 如果过滤表达式含有shell元字符,考虑使用单引号引起来。可以同时指定多个 过滤表达式,用空格做分隔,之间是逻辑与的关系。 举例 tcpdump host scz tcpdump net ucb-ether tcpdump 'gateway evil and (port ftp or ftp-data)' tcpdump ip and not net localnet tcpdump 'tcp[13] & 3 != 0 and not src and dst net localnet' tcpdump 'gateway snup and ip[2:2] > 576' tcpdump 'ether[0] & 1 = 0 and ip[16] >= 224' tcpdump 'icmp[0] != 8 and icmp[0] != 0' 参看 bpf(4)、pcap(3) ftp://ftp.ee.lbl.gov/tcpdump.tar.Z BUGS NIT无法捕获自身的outbound packet,BPF可以。 不支持IP碎片重组 反向域名解析有问题 跨午夜0点的抓包,时间戳将混乱 对IPv6的支持不成熟 -------------------------------------------------------------------------- 10.17 Solaris系统中ip_strict_dst_multihoming的确切含义是什么 Q: 某些安全配置文档中看到如下建议 /usr/sbin/ndd -get /dev/ip ip_strict_dst_multihoming (系统缺省设置为0) 在/etc/rc2.d/S69inet启动脚本中增加如下行 /usr/sbin/ndd -set /dev/ip ip_strict_dst_multihoming 1 ip_strict_dst_multihoming这个参数的确切含义是什么 A: Casper Dik 1999-09-22 先举个例子,UDP报文的源IP很容易伪造成127.0.0.1,这对RPCBIND的PMAPPROC_SET、 PMAPPROC_UNSET过程非常不利,攻击者可以利用这两个远程过程实施拒绝服务攻击。 假设作了如上设置,当报文的源为本地IP却又来自错误的网络接口时,内核将丢弃这 样的报文。 10.18 Linux下网卡重命名 A: lgx@nsfocus 2005-11-22 10:09 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -s -o ifname ifname.c */ #include #include #include #include #include #include #include #include int main ( int argc, char * argv[] ) { int s; struct ifreq ifr; if ( 3 != argc ) { fprintf( stderr, "Usage: %s old_name new_name\n", argv[0] ); return( -1 ); } s = socket( PF_INET, SOCK_DGRAM, 0 ); /* * 这里有溢出,懒得管了 */ strcpy( ifr.ifr_name, argv[1] ); strcpy( ifr.ifr_newname, argv[2] ); if ( ioctl( s, SIOCSIFNAME, &ifr ) < 0 ) { perror( "SIOCSIFNAME" ); } close( s ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- # ifconfig eth1 down # ./ifname eth1 lgx # ifconfig lgx up 11. package相关问题 11.0 在SPARC/Solaris 8上手工安装libpcap A: 小四 1) 安装flex(代替lex) wget --passive-ftp -c -t0 ftp://ftp.gnu.org/pub/non-gnu/flex/flex-2.5.4a.tar.gz gzip -cd flex-2.5.4a.tar.gz | tar xvf - cd flex-2.5.4 ./configure make CC=gcc prefix=/usr clean all make prefix=/usr install 2) 安装bison(代替yacc) wget --passive-ftp -c -t0 ftp://ftp.gnu.org/gnu/bison/bison-1.35.tar.gz gzip -cd bison-1.35.tar.gz | tar xvf - cd bison-1.35 ./configure make CC=gcc prefix=/usr clean all make prefix=/usr install 3) 安装libpcap wget http://www.tcpdump.org/release/libpcap-0.6.2.tar.gz gzip -cd libpcap-0.6.2.tar.gz | tar xvf - cd libpcap-0.6.2 ./configure make CC=gcc prefix=/usr clean depend all make prefix=/usr install ls -l /usr/include/pcap.h ls -l /usr/lib/libpcap.a 4) 安装rid-1.11.tgz wget http://www.theorygroup.com/Software/RID/rid-1.11.tgz gzip -cd rid-1.11.tgz | tar xvf - cd rid-1.11 LIBS="-lsocket -lnsl" ./configure make 这个包的configure脚本在判断-lpcap是否存在时有误。执行./configure时即使存在 libpcap库,也会看到 checking for pcap_datalink in -lpcap... no 此时检查config.log文件,含有如下内容 configure:1144: gcc -o conftest -g -O2 conftest.c -lpcap 1>&5 Undefined first referenced symbol in file getprotobyname /usr/lib/libpcap.a(nametoaddr.o) gethostbyname /usr/lib/libpcap.a(nametoaddr.o) getservbyname /usr/lib/libpcap.a(nametoaddr.o) getnetbyname /usr/lib/libpcap.a(nametoaddr.o) 这表明了configure脚本如何判断-lpcap是否存在。对于SPARC/Solaris,这显然有误, 应该指定"-lsocket -lnsl"链接开关。检查configure脚本,指定LIBS变量即可,如 上所示。 11.1 Solaris下如何将二进制软件包安装到指定目标路径下 Q: pkgadd无法指定目标路径,而我不想使用缺省安装路径/usr/local A: lisuit@水木清华 2002-01-19 14:49 pkgadd -R ... 11.2 Solaris下如何自己定制二进制安装包 A: deepin & scz [root@ /tmp/tools]> mkdir srcroot [root@ /tmp/tools]> cp -p /usr/ccs/bin/as srcroot <-- 复制那些数据源到该目录下 [root@ /tmp/tools]> cp -p /usr/ccs/bin/make srcroot [root@ /tmp/tools]> cp -p /usr/ccs/lib/cpp srcroot [root@ /tmp/tools]> ls -lR srcroot -rwxr-xr-x 1 bin bin as* -rwxr-xr-x 1 bin bin cpp* -rwxr-xr-x 1 bin bin make* [root@ /tmp/tools]> cd srcroot [root@ /tmp/tools/srcroot]> find . -print | pkgproto > ../prototype [root@ /tmp/tools/srcroot]> cd .. [root@ /tmp/tools]> more prototype f none cpp 0755 bin bin f none as 0755 bin bin f none make 0755 bin bin [root@ /tmp/tools]> vi prototype <-- 在头部增加如下行 i pkginfo=./pkginfo [root@ /tmp/tools]> vi pkginfo PKG="Scz" NAME="Scz Package Cool Tools" VERSION="2000-01-09 14:53" CATEGORY="application" PSTAMP="小四" CLASSES="none" ARCH="Solaris 7 For SPARC" VENDOR="NSFocus" BASEDIR="/tmp/tools/opt/" EMAIL="scz@nsfocus.com" [root@ /tmp/tools]> pkgmk -o -f ./prototype -b /tmp/tools/srcroot [root@ /tmp/tools]> pkgtrans -s /var/spool/pkg /tmp/tools/SczPkg Scz [root@ /tmp/tools]> rm -rf /var/spool/pkg/Scz [root@ /tmp/tools]> pkgadd -d SczPkg [root@ /tmp/tools]> ls -lR opt -rwxr-xr-x 1 bin bin as* -rwxr-xr-x 1 bin bin cpp* -rwxr-xr-x 1 bin bin make* [root@ /tmp/tools]> pkginfo -l Scz [root@ /tmp/tools]> pkgrm Scz [root@ /tmp/tools]> rm SczPkg Q: 要在安装包里包含某些配置文件(比如xxx.conf),安装时要让它安装到/etc目录, 怎么做 A: 小四 2002-05-15 10:05 pkginfo(4)中的BASEDIR只影响相对路径,可以直接在prototype(4)中指定绝对路径 (比如/etc/xxx.conf)。如果打包时/etc/xxx.conf就存在,直接写 f none /etc/xxx.conf 0755 bin bin 如果打包时xxx.conf位于/tmp/tools/srcroot下,又不想或不能放到/etc/下去,可 以写成 f none /etc/xxx.conf=xxx.conf 0755 bin bin 这个格式是 = Q: 如何单独抽取安装包中的某个文件 A: scz 2004-03-16 16:23 继续以上例中的SczPkg为例。 [root@ /tmp/tools]> ls ./ ../ SczPkg [root@ /tmp/tools]> pkgadd -s /tmp/tools -d SczPkg (伪安装后reloc子目录中即最终文件) Transferring package instance [root@ /tmp/tools]> ls -lR .: total 1456 drwxr-xr-x 3 scz other 240 Mar 16 16:20 ./ drwxrwxrwt 6 root sys 439 Mar 16 16:20 ../ drwxr-xr-x 3 root other 306 Mar 16 16:20 Scz/ -rw-r--r-- 1 scz other 719360 Mar 16 16:00 SczPkg ./Scz: total 80 drwxr-xr-x 3 root other 306 Mar 16 16:20 ./ drwxr-xr-x 3 scz other 240 Mar 16 16:20 ../ -rw-r--r-- 1 scz other 193 Mar 16 16:00 pkginfo -rw-r--r-- 1 scz other 193 Mar 16 16:00 pkgmap drwxr-xr-x 2 scz other 297 Mar 16 16:20 reloc/ ./Scz/reloc: total 1456 drwxr-xr-x 2 scz other 297 Mar 16 16:20 ./ drwxr-xr-x 3 root other 306 Mar 16 16:20 ../ -rw-r--r-- 1 scz other 355348 Aug 20 1999 as -rw-r--r-- 1 scz other 91344 Jan 11 1999 cpp -rw-r--r-- 1 scz other 269520 Sep 14 1999 make [root@ /tmp/tools]> pkgrm -s /tmp/tools Scz Removing spooled package instance [root@ /tmp/tools]> pkgtrans -o SczPkg /tmp/tools/ Scz (这是第二种办法) Transferring package instance [root@ /tmp/tools]> ls -lR Scz Scz: total 80 drwxr-xr-x 3 root other 306 Mar 16 16:23 ./ drwxr-xr-x 3 scz other 240 Mar 16 16:23 ../ -rw-r--r-- 1 scz other 193 Mar 16 16:00 pkginfo -rw-r--r-- 1 scz other 193 Mar 16 16:00 pkgmap drwxr-xr-x 2 scz other 297 Mar 16 16:23 reloc/ Scz/reloc: total 1456 drwxr-xr-x 2 scz other 297 Mar 16 16:23 ./ drwxr-xr-x 3 root other 306 Mar 16 16:23 ../ -rw-r--r-- 1 scz other 355348 Aug 20 1999 as -rw-r--r-- 1 scz other 91344 Jan 11 1999 cpp -rw-r--r-- 1 scz other 269520 Sep 14 1999 make [root@ /tmp/tools]> rm -rf Scz 11.3 如何恢复/usr/bin/su的缺省安装属性 Q: 我怀疑/usr/bin/su被修改过了,如何证实呢 A: John D Groenveld $ pkgchk -l -p /usr/bin/su Pathname: /usr/bin/su Type: regular file Expected mode: 4511 Expected owner: root Expected group: sys Expected file size (bytes): 18360 Expected sum(1) of contents: 32865 Expected last modification: 1月 15 09时 18分 46秒 1998 Referenced by the following packages: SUNWcsu Current status: installed man -s 1M pkgchk 了解更多信息 A: Roland Titze 检查文件 /var/sadm/install/contents ,安装包信息以文本形式存放在这个文件中 $ grep "/usr/bin/su " /var/sadm/install/contents /sbin/su=../usr/bin/su s none SUNWcsr /usr/bin/su f none 4511 root sys 18360 32865 884884726 SUNWcsu A: scz # ls -l /var/sadm/install/contents -rw-r--r-- 1 root other /var/sadm/install/contents # 注意,由于pkgadd会更新这个文件,而这个文件是root才可写的,所以非root用户无 法正常使用pkgadd安装软件包。 关于校验和计算,参看cksum(1) sum(1B) sum(1)等命令的手册页。pkgchk为木马检 测提供了一种可能的技术,但是由于文本文件/var/sadm/install/contents本身易于 修改,也不可靠。我的建议是提前备份/var/sadm/install/contents文件,至少需要 针对该文件做一定保护,很多系统文件的校验和信息就不用写脚本备份到自己的文件 中了。如果安装木马的人没有意识到这个文件的存在和意义,就给入侵检测提供了机 会。反之,如果是入侵企图,看到这里的朋友应该修正/var/sadm/install/contents 文件。无论是检测还是入侵,都可以写个脚本来完成,sed命令足够了。 Q: 如何恢复文件权限属性 A: dkoleary@mediaone.net 2001-06-02 22:41 所有经pkgadd安装的文件,在/var/sadm/install/contents中记录有文件名、原始文 件权限属性等信息。 ok boot cdrom -s mount / 到 /a,/usr 到 /a/usr,/var 到 /a/var,/opt 到/a/opt,执行 # pkgchk -R /a -f 将根据/var/sadm/install/contents文件进行文件权限属性恢复。这里有个例外,所 有suid、sgid、sticky位并不设置,必须写个脚本根据/var/sadm/install/contents 文件自行恢复。 11.4 如何获知指定包与其他包之间的依赖关系 A: Vitaly Filatov 详细信息查看 /var/sadm/pkg//install/depend 文件,这里指明了该包的 三个特性 P 安装前必须已经存在的包 I 与自己不兼容的包 R 依赖自己的其他包 11.5 如何获得Linux命令的源代码 Q: Linux中如何知道ifconfig属于哪个包 A: # which ifconfig /sbin/ifconfig # rpm -qf /sbin/ifconfig net-tools-1.51-3 <-- rpm包名字 # rpm -qi net-tools-1.51-3 或者 # rpm -qif /sbin/ifconfig <-- 直接查找src.rpm Q: 如何获取ps命令的源代码 A: dfbb@SMTH 2003-04-23 1) $ rpm -qif `which ps` | grep "Source RPM" | awk '{print $NF}' procps-2.0.7-12.src.rpm 2) 去rpmfind.net或者sourceforge.net搜索关键字"procps" 3) 下载procps-2.0.7-12.src.rpm 4) $ rpm2cpio procps-2.0.7-12.src.rpm | cpio -idv $ tar xvfz procps-2.0.7.tar.gz 11.6 Solaris下如何知道某包中有哪些文件 A: 小四 2001-12-10 21:39 1) 已经安装了的包,对/var/sadm/install/contents文件做grep操作 # grep SUNWcsd /var/sadm/install/contents ~~~~~~~ 包名 2) 尚未安装的包,先伪安装,然后检查pkgmap文件 # pkgadd -s /var/spool/pkg -d less-340-sol7-sparc-local # cd /var/spool/pkg/GNUless # grep BASEDIR pkginfo <-- 确认BASEDIR目录,比如/usr/local # vi pkgmap <-- 注意第四列 # rm -rf /var/spool/pkg/GNUless 可以写个脚本完成这些工作。其实伪安装后reloc子目录中即最终文件。 11.7 RedHat下如何检查文件是否被改动过 Q: 我怀疑自己的/bin/login被入侵者替换过了,如何确认这点。 A: jbtzhm & lgx 1) rpm -V `rpm -qf ` 任何输出的文件名(非包名)均表示其被改动过,正常情况下应该无任何文件名输 出。对于RedHat 8,可以简写成: rpm -qfV `which login` 2) ls -lc `which login` ctime相对不易被修改,简单touch操作无法修改ctime,可做参考。 3) strings -a -n 3 `which login` | more 4) nm -a `which login` | more 5) file `which login` 如果确认被改动过,可以用IDA Pro、GDB、objdump等工具进一步反汇编检查。 12. 日志相关问题 12.0 Solaris 8如何enable FTP session log Q: Solaris 8如何enable FTP session log,就是记录FTP用户执行了哪些命令 A: root from gceclub.sun.com.cn 2002-10-11 11:56 参看http://www.ebsinc.com/solaris/network/ftp.html 编辑/etc/inetd.conf,修改如下(在行尾增加一个"-d") ftp stream tcp6 nowait root /usr/sbin/in.ftpd in.ftpd -d 编辑/etc/syslog.conf,增加如下行 daemon.debug /var/adm/ftp-debug.log 然后重启inetd和syslogd # kill -HUP # kill -HUP (有时候这里可能需要彻底杀掉syslogd再重启) 从此/var/adm/ftp-debug.log中将记录FTP用户执行了哪些命令,比如 Oct 4 10:22:23 psi ftpd[8080]: FTPD: command: USER appel Oct 4 10:22:23 psi ftpd[8080]: <--- 530 Oct 4 10:22:23 psi ftpd[8080]: User appel access denied. Oct 4 10:22:24 psi ftpd[8080]: FTPD: command: QUIT Oct 4 10:22:24 psi ftpd[8080]: <--- 221 Oct 4 10:22:24 psi ftpd[8080]: Goodbye. 12.1 如何查看/var/adm/utmp、/var/adm/wtmp、/var/adm/lastlog Q: 如何查看utmp、utmpx、wtmp、wtmpx、lastlog文件 A: senthilkumar /usr/lib/acct/fwtmp /usr/lib/acct/wtmpfix 可以用fwtmp命令读取这些文件,具体细节参看fwtmp(1M)手册页。 # cat /var/adm/utmp | /usr/lib/acct/fwtmp Q: 我用echo "" > /var/adm/utmpx命令试图清空这些文件(lastlog、utmp、utmpx、 wtmp、wtmpx),结果导致无法telnet/ftp到这台主机。最后我被迫恢复原来的文 件。当我telnet登录时,报告"No utmpx entry. You must exec login from the lowest level shell",什么意思? A: senthilkumar "No utmpx entry"意味着文件系统满,没有空间记录utmpx入口点(登录信息)。此时 应该进入单用户模式(Stop-A后boot -s),清空(不是删除)两个文件:/var/adm/utmp 和/var/adm/utmpx # cat /dev/null > /var/adm/utmp # cat /dev/null > /var/adm/utmpx 这两条命令清空文件但是保持原有正确属性。某些时候清空这两个文件之后/var文件 系统依旧满,此时可以 # du -askd /var | sort -nr | more 将从大到小报告/var文件系统中的文件列表,为了腾出空间,可以清空 /var/cron/log、/var/spool/lp/logs、/var/adm/messages。还可以检查 /.wastebasket文件,找出那些可以删除的大文件。 D: scz # du -ks * | sort -nr | more 将从大到小显示当前目录下各个子目录的统计信息,以KB为单位。 12.2 logger/syslogd问题 Q: 我在缺省/etc/syslog.conf文件中增加了如下行 local0.* /var/adm/esnmessages 然后kill -HUP `cat /etc/syslog.pid`,接着使用命令 /usr/bin/logger -p local0.notice test 测试。结果发现前面的修改无效,/var/adm/esnmessages并未更新。 A: Steven Buehrle 你仔细查看syslog.conf的手册页了吗?所增加的条目无效,星号*可以用于指定 facility,但是不能用于指定level。 12.3 如何关闭cron的日志 Q: 有些时候cron的日志文件增长得如此之大,占用了大量磁盘空间,有什么办法彻 底关闭cron的日志吗 A: Sun Microsystems 1998-03-30 编辑/etc/default/cron,设置 CRONLOG 变量为 NO ,将关闭cron的日志 CRONLOG=NO 缺省是 CRONLOG=YES 12.4 /var/adm/lastlog文件看上去太大了 A: Sun Microsystems # truss -vlstat -tlstat ls -l /var/adm/lastlog lstat64("/var/adm/lastlog", 0xEFFFFC18) = 0 d=0x025C0000 i=29730 m=0100444 l=1 u=0 g=0 sz=1680028 at = Jun 20 17:34:09 GMT 2001 [ 993087249 ] mt = Jun 20 17:34:09 GMT 2001 [ 993087249 ] ct = Jun 20 17:34:09 GMT 2001 [ 993087249 ] bsz=8192 blks=64 fs=ufs -r--r--r-- 1 root root 1680028 6月 20 17:34 /var/adm/lastlog # truss -vlstat -tlstat ls -ls /var/adm/lastlog lstat64("/var/adm/lastlog", 0xEFFFFC18) = 0 d=0x025C0000 i=29730 m=0100444 l=1 u=0 g=0 sz=1680028 at = Jun 20 17:34:09 GMT 2001 [ 993087249 ] mt = Jun 20 17:34:09 GMT 2001 [ 993087249 ] ct = Jun 20 17:34:09 GMT 2001 [ 993087249 ] bsz=8192 blks=64 fs=ufs 64 -r--r--r-- 1 root root 1680028 6月 20 17:34 /var/adm/lastlog # 参看stat(2)手册页、/usr/include/lastlog.h头文件。/var/adm/lastlog是一个稀 疏文件,其中有很多"空洞",ls -l显示的是st_size成员,st_blocks成员才代表文 件实际占用磁盘空间大小(以512字节为单位)。 13. 进程相关问题 13.1 如何根据进程名获得PID Q: 我知道ps、top等命令和grep相结合可以达到这个效果,但是我想在C程序中实现 这个功能,并且我不想用system()、popen()等方式。 D: Linux提供了一个命令,pidof(8) A: Andrew Gierth 第一种办法是读取/proc接口提供的信息 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o getpid getpid.c */ #include #include #include #include #include #include #include #include #include static pid_t getpidbyname ( char * name, pid_t skipit ) { DIR * dirHandle; /* 目录句柄 */ struct dirent * dirEntry; /* 单个目录项 */ prpsinfo_t prp; int fd; pid_t pid = -1; if ( ( dirHandle = opendir( "/proc" ) ) == NULL ) { return( -1 ); } /* * 下面使用相对路径打开文件,所以必须进入/proc */ chdir( "/proc" ); while ( ( dirEntry = readdir( dirHandle ) ) != NULL ) { if ( dirEntry->d_name[0] != '.' ) { /* * fprintf( stderr, "%s\n", dirEntry->d_name ); */ if ( ( fd = open( dirEntry->d_name, O_RDONLY ) ) != -1 ) { if ( ioctl( fd, PIOCPSINFO, &prp ) != -1 ) { /* * fprintf( stderr, "%s\n", prp.pr_fname ); */ /* * 这里是相对路径,而且不带参数 */ if ( !strcmp( prp.pr_fname, name ) ) { pid = ( pid_t )atoi( dirEntry->d_name ); /* * -1做为无效pid对待 */ if ( skipit != -1 && pid == skipit ) { pid = -1; } else { /* * 找到匹配 */ close( fd ); /* * 跳出while循环 */ break; } } } close( fd ); } } } /* end of while */ closedir( dirHandle ); return( pid ); } /* end of getpidbyname */ static void usage ( char * arg ) { fprintf( stderr, " Usage: %s \n", arg ); exit( EXIT_FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { pid_t pid; if ( argc != 2 ) { usage( argv[0] ); } pid = getpidbyname( argv[1], -1 ); if ( pid != -1 ) { fprintf( stderr, "[ %s ] is: <%u>\n", argv[1], ( unsigned int )pid ); return( EXIT_SUCCESS ); } return( EXIT_FAILURE ); } /* end of main */ -------------------------------------------------------------------------- 这种技术要求运行者拥有root权限,否则无法有效获取非自己拥有的进程PID。注意 下面的演示 # ps -f -p 223 UID PID PPID C STIME TTY TIME CMD root 223 1 0 3月 09 ? 0:00 /usr/sbin/vold # ./getpid /usr/sbin/vold <-- 这个用法无法找到匹配 # ./getpid vold <-- 只能匹配相对路径 [ vold ] is: <223> 当然你可以自己修改、增强程序,使之匹配各种命令行指定,我就不替你做了。上述 程序在32-bit kernel的Solaris 2.6和64-bit kernel的Solaris 7上均测试通过。 D: microcat 在介绍第二种办法之前,先看一下microcat提供的这个程序 -------------------------------------------------------------------------- /* * gcc -DSOLARIS=6 -Wall -pipe -O3 -o listpid listpid.c -lkvm * * /opt/SUNWspro/SC5.0/bin/cc -DSOLARIS=7 -xarch=v9 -O -o listpid listpid.c -lkvm */ #include #include #include #include #include int main ( int argc, char * argv[] ) { kvm_t * kd; struct proc * p; struct pid pid; if ( ( kd = kvm_open( NULL, NULL, NULL, O_RDONLY, NULL ) ) == NULL ) { perror( "kvm_open error" ); return( EXIT_FAILURE ); } /* * 遍历P区 */ while ( ( p = kvm_nextproc( kd ) ) ) { #if SOLARIS == 7 if ( kvm_kread( kd, ( uintptr_t )p->p_pidp, &pid, sizeof( pid ) ) < 0 ) #elif SOLARIS == 6 if ( kvm_kread( kd, ( unsigned long )p->p_pidp, ( char * )&pid, sizeof( pid ) ) < 0 ) #endif { perror( "kvm_kread error" ); } else { printf( "PID: %d\n", ( int )pid.pid_id ); } } /* end of while */ kvm_close( kd ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 这个程序必须以root身份运行,我在SPARC/Solaris 2.6/7上测试通过,注意编译命 令不同,如果你想在64-bit kernel的Solaris 7上运行这个程序,gcc 2.95.2是无能 为力的,即使使用/opt/SUNWspro/SC5.0/bin/cc,也必须指定-xarch=v9编译64-bit 代码,才能正常运行。 A: Andrew Gierth 第二种办法是使用kvm_*()函数 -------------------------------------------------------------------------- /* * gcc -Wall -DSOLARIS=6 -pipe -O3 -o getpid getpid.c -lkvm -lgen * * /opt/SUNWspro/SC5.0/bin/cc -DSOLARIS=7 -xarch=v9 -O -o getpid getpid.c -lkvm -lgen */ /* * 必须定义这个宏 */ #define _KMEMUSER #include #include #include #include #include #include /* static void argv_free ( char ** argv ) { size_t i; for ( i = 0; argv[i] != NULL; i++ ) { free( argv[i] ); argv[i] = NULL; } free( argv ); } */ static pid_t getpidbyname ( char *name, pid_t skipit ) { kvm_t *kd; int error; char **argv = NULL; char *p_name = NULL; pid_t pid = -1; char expbuf[256]; char regexp_str[256]; struct user *cur_user; struct proc *cur_proc; struct pid p; sprintf( regexp_str, "^.*%s$", name ); /* * 正则表达式 */ if ( compile( regexp_str, expbuf, expbuf + 256 ) == NULL ) { perror( "compile" ); return( -1 ); } if ( ( kd = kvm_open( NULL, NULL, NULL, O_RDONLY, NULL ) ) == NULL ) { perror( "kvm_open" ); return( -1 ); } /* * 遍历P区 */ while ( ( cur_proc = kvm_nextproc( kd ) ) ) { /* * 注意理解这里为什么要使用kvm_kread()函数,否则很容易出现段故障 */ #if SOLARIS == 7 if ( kvm_kread( kd, ( uintptr_t )cur_proc->p_pidp, &p, sizeof( p ) ) < 0 ) #elif SOLARIS == 6 if ( kvm_kread( kd, ( unsigned long )cur_proc->p_pidp, ( char * )&p, sizeof( p ) ) < 0 ) #endif { perror( "kvm_kread" ); continue; } pid = p.pid_id; if ( ( cur_user = kvm_getu( kd, cur_proc ) ) != NULL ) { /* fprintf( stderr, "cur_proc = %p cur_user = %p\n", cur_proc, cur_user ); */ error = kvm_getcmd( kd, cur_proc, cur_user, &argv, NULL ); /* * fprintf( stderr, "[ %s ] is: <%u>\n", cur_user->u_comm, ( unsigned int )pid ); * * 比如in.telnetd、syslogd、bash、login */ if ( error == -1 ) { /* * 失败,比如argv[]已经被进程自己修改过 */ if ( cur_user->u_comm[0] != '\0' ) { /* * 从另外一个地方获取信息 */ p_name = cur_user->u_comm; } } /* * 成功 */ else { /* * fprintf( stderr, "[ %s ] is: <%u>\n", argv[0], ( unsigned int )pid ); * * 比如-bash、login、in.telnetd、/usr/sbin/syslogd */ p_name = argv[0]; } } if ( p_name ) { if ( ( strcmp( p_name, name ) == 0 ) || step( p_name, expbuf ) ) { /* * -1做为无效pid对待 */ if ( skipit != -1 && pid == skipit ) { pid = -1; } /* * 找到匹配,返回pid */ else { /* * 跳出while循环 */ break; } } } if ( argv != NULL ) { /* * argv_free( argv ); */ free( argv ); argv = NULL; } /* * 必须增加这条,否则流程有问题 */ p_name = NULL; } /* end of while */ if ( argv != NULL ) { /* * 根据kvm_getu(3K)手册页的描述,我企图释放指针数组的各个元素以及 * 指针数组本身。测试下来,只能释放指针数组本身,无法单独释放各个 * 元素,否则出现总线错误。个人认为kvm_getu(3K)手册页的描述是正确 * 的,有待进一步测试 */ /* * argv_free( argv ); */ free( argv ); argv = NULL; } kvm_close( kd ); return( pid ); } /* end of getpidbyname */ static void usage ( char * arg ) { fprintf( stderr, " Usage: %s \n", arg ); exit( EXIT_FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { pid_t pid; if ( argc != 2 ) { usage( argv[0] ); } pid = getpidbyname( argv[1], -1 ); if ( pid != -1 ) { fprintf( stderr, "[ %s ] is: <%u>\n", argv[1], ( unsigned int )pid ); return( EXIT_SUCCESS ); } return( EXIT_FAILURE ); } /* end of main */ -------------------------------------------------------------------------- 这个程序同样必须以root身份运行,在SPARC/Solaris 2.6/7上测试通过,注意编译 命令不同。 D: scz Andrew Gierth在<>中提供的例子有点问题,可能是 不同平台上测试的。很多kvm编程初学者在Solaris 2.6上测试时碰上段故障错误,主 要在于kvm_nextproc()获得P区结构后,其中的指针是内核空间的指针,不能直接在 用户空间使用,必须用kvm_kread()函数将相应数据从内核空间读取到用户空间来, 多次在CERNET BBS上碰到这种问题,这次一并解答。 其次,原例子中关于正则表达式的处理和应用有点问题,程序流程中事实上并没有达 到期待效果,现在我修正后的这个例子已经在做正则表达式匹配。 最后,参看上述代码中的注释。根据kvm_getu(3K)手册页的描述,我企图释放指针数 组的各个元素以及指针数组本身,提供了一个argv_free()函数。测试下来,只能释 放指针数组本身,无法单独释放各个元素,否则出现总线错误。个人认为 kvm_getu(3K)手册页的描述是正确的,有待进一步测试。 参看kvm_read(3K)手册页,kvm_read()不推荐使用,代之以kvm_kread(),如果读取 通过kvm_getu(3K)获取的进程U区,推荐使用kvm_uread()。 13.2 如何在命令行上访问指定进程P、U两区,如何欺骗Solaris的ps A: scz 2001-05-31 17:18 参看/usr/include/sys/proc.h和/usr/include/sys/user.h # ps -o addr -p 1 ADDR 300001c9488 # ~~~~~~~~~~~ 这个指针指向指定进程的P区 在SPARC/Solaris 7 64-bit kernel mode下,P + 744 --> U,所以此时 0x300001c9488 + 0x2E8 = 0x300001c9770 --> U区 ( P + 0x2E8 ) ~~~~~~~~~ 0x300001c9770 + 0x0A0 = 0x300001c9810 --> auxv_t u_auxv[19] 每个结构16个字节,总共 19 * 16 = 304 = 0x130 大小 0x300001c9770 + 0x1D0 = 0x300001c9940 --> char u_psargs[80] ( P + 0x4B8 ) ~~~~~~~~~ 0x300001c9770 + 0x220 = 0x300001c9990 --> char u_comm[17]; ( P + 0x508 ) ~~~~~~~~~ # echo "c 16i 10o 9488 508 + p c" | /usr/bin/dc <-- 可以用dc完成这个加法 9990 # 参看/usr/include/sys/procfs.h中psinfo_t结构定义 ps显示CMD的时候,如果指定了-f,显示 #define PRARGSZ 80 /* number of chars of arguments */ char pr_psargs[PRARGSZ]; /* initial characters of arg list */ 如果没有指定-f参数,ps显示CMD时使用 #define PRFNSZ 16 /* Maximum size of execed filename */ char pr_fname[PRFNSZ]; /* name of execed file */ 这两处数据实际上就是来自u_psargs[80]和u_comm[17]。 Ok,现在问题明朗化,如果在Solaris下要欺骗ps的CMD显示,修改argv[]无用,需要 修改U区的u_psargs[80]和u_comm[17]。这两处数据用于内核维护进程信息,进程本 身并不依赖它们,可放心修改之。注意ASCIIZ串要以'\0'结束。FreeBSD的情况类似, Sun OS 4.x以BSD为蓝本,二者类似很正常。 13.3 getexecname(3C)是怎么实现的 13.4 Solaris 7/8下ps输出中的问号 Q: 比如ps -el的输出中有很多问号,可我觉得它们应该有一个确定的值 A: Michael Shapiro 有些时候ps(1)输出的单行过于长了,为了输出美观,某些列的值用问号代替,尤 其64-bit内核下ADDR列。可以用-o参数指定要显示的列,比如 # ps -o pid,tty,addr,wchan,fname -p $$ PID TT ADDR WCHAN COMMAND 2602 pts/4 30000a154b8 30000a15578 bash # ps -e -o pid,tty,addr,wchan,fname 13.5 如何根据某种原则终止一批进程 Q: 如何根据某种原则终止一批进程,比如 $ ps -ef | grep oba root 350 316 0 feb 01 ? 11:42 /usr/openwin/bin/Xsun :0 -nobanng root 14740 14739 0 04:00:00 ? 0:33 /u2/app/bse/bin/bshell6.1 14736 0 root 26031 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban2 root 27704 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban4 root 26055 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban0 root 14739 14736 0 04:00:00 ? 0:00 /usr/bin/ksh /u2/app/bse/bin/bsh0 root 26045 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban6 oracle 15991 15840 0 09:53:40 pts/4 0:00 grep oba root 26023 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban8 root 14736 1 0 04:00:00 ? 0:00 /u2/app/bse/bin/ba6.1 obaxml1010 root 26063 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban4 root 25993 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban0 root 26010 1 0 mar 06 ? 0:00 lp -c -d hplj4_b10lager -o noban4 有没有可能终止 - 所有在今天之前启动的进程 - 所有最后一列包含"lp -c"的进程 A: Z. Majeed 我不知道是否有现成的命令达到这个目的,但是一个利用/proc的小脚本足够了。用 指定时间戳创建一个文件,便于我们终止那些在此之前启动的进程 $ touch -t 200103140000 /tmp/before $ ls -l /tmp/before -rw-r--r-- 1 owner group 0 Mar 14 00:00 /tmp/before $ cd /proc $ for p in *; do [ $p -ot /tmp/before ] && ps -f -p $p; done 如果显示出来的进程列表是你所期望的,将"ps -f -p"替换成"kill"即可。 pgrep -f "lp -c" 将显示所有匹配进程,用pkill代替pgrep就可以杀死进程。 13.6 利用libproc库编程举例 Q: Sun提供了新的/proc文件系统(procfs)以及/usr/proc/bin/目录下的工具,据说 它们利用了libproc库,可我找不到相关文档 A: Sun Microsystems 1999-07-01 下面是一个简单的演示程序,利用了libproc库。注意,该接口从7到8已经发生变化, 可能还要等它稳定下来才会提供公开的文档。 -------------------------------------------------------------------------- /* * 演示用,不要在产品中使用,只能32-bit编译,不能64-bit编译 * * 该程序使用了libproc.so,这是一个未文档化的、非公开的编程接口,在将来会 * 发生变化 * * Solaris 7 : gcc -Wall -DSOLARIS_7 -pipe -O3 -o procdemo procdemo.c -lproc * Solaris 8 : gcc -Wall -DSOLARIS_8 -pipe -O3 -o procdemo procdemo.c -lproc */ #include #include #include #include #include #include #include #include #define PGRAB_RETAIN 0x01 #ifdef SOLARIS_8 #define POBJNAME Pobjname #else #define POBJNAME proc_objname #endif int main ( int argc, char * argv[] ) { pid_t pid; char filename[1024]; char buf[1024]; int bufsize = sizeof( buf ); int proc_err = 0; int fd, len; prmap_t proc_map; struct ps_prochandle *prochandle; if ( argc != 2 ) { fprintf( stderr, " Usage: %s \n", argv[0] ); exit( EXIT_FAILURE ); } pid = atoi( argv[1] ); sprintf( filename, "/proc/%d/map", pid ); fd = open( filename, O_RDONLY ); if ( NULL == ( prochandle = ( struct ps_prochandle * )Pgrab( pid, PGRAB_RETAIN, &proc_err ) ) ) { fprintf( stderr, "%s: cannot grab %d: %d\n", argv[0], pid, proc_err ); exit( EXIT_FAILURE ); } while ( read( fd, &proc_map, sizeof( proc_map ) ) ) { POBJNAME( prochandle, proc_map.pr_vaddr, buf, bufsize ); if ( ( len = resolvepath( buf, buf, bufsize ) ) > 0 ) { buf[ len ] = '\0'; } /* fprintf( stderr, "pr_mapname : %24s pr_size : %7d objname : %s\n", proc_map.pr_mapname, proc_map.pr_size, buf ); */ fprintf( stderr, "%08X %7d %08X %s\n", proc_map.pr_vaddr, proc_map.pr_size, proc_map.pr_mflags, buf ); } return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- D: scz # find /usr/include -type f -name "*.h" | xargs grep "} prmap_t" /usr/include/sys/old_procfs.h:} prmap_t; /usr/include/sys/procfs.h:} prmap_t; # -------------------------------------------------------------------------- /* * Memory-map interface. /proc//map /proc//rmap */ #define PRMAPSZ 64 typedef struct prmap { uintptr_t pr_vaddr; /* virtual address of mapping */ size_t pr_size; /* size of mapping in bytes */ char pr_mapname[PRMAPSZ]; /* name in /proc//object */ offset_t pr_offset; /* offset into mapped object, if any */ int pr_mflags; /* protection and attribute flags (see below) */ int pr_pagesize; /* pagesize (bytes) for this mapping */ int pr_shmid; /* SysV shmid, -1 if not SysV shared memory */ int pr_filler[1]; /* filler for future expansion */ } prmap_t; /* Protection and attribute flags */ #define MA_READ 0x04 /* readable by the traced process */ #define MA_WRITE 0x02 /* writable by the traced process */ #define MA_EXEC 0x01 /* executable by the traced process */ #define MA_SHARED 0x08 /* changes are shared by mapped object */ #define MA_ANON 0x40 /* anonymous memory (e.g. /dev/zero) */ /* * These are obsolete and unreliable. * They are included here only for historical compatibility. */ #define MA_BREAK 0x10 /* grown by brk(2) */ #define MA_STACK 0x20 /* grown automatically on stack faults */ -------------------------------------------------------------------------- 13.7 给定一个PID,如何知道它对应一个运行中的进程 A: Andrew Gierth 这个回答来自著名的<>,由Andrew Gierth负责维 护,其它细节请参看原文。 kill( pid, 0 ),此时有四种可能的返回值 1) kill()返回0 意味着指定PID的确对应着一个运行中的进程,系统允许你向该进程发送信号。至 于该进程能否是zombie process(僵尸进程),是系统相关的。 2) kill()返回-1,errno == ESRCH 指定PID并不对应一个运行中的进程,或者权限不够无法完成判断。某些系统上, 如果对应进程是僵尸进程时,也如此返回。 3) kill()返回-1,errno == EPERM 系统不允许你kill指定进程,进程存在(可能是zombie),权限不够。 4) kill()返回-1,errno是其它值 你麻烦来了(嘿嘿) 最有用的技术,假设成功表示进程存在,EPERM失败也表示进程存在,其它失败表示 指定PID不对应一个运行中的进程。 此外如果系统支持proc伪文件系统,检查/proc/是否存在,存在表明指定PID对 应运行中的进程。 13.8 Unix编程中所谓"僵尸进程"指什么 Q: Unix编程中所谓"僵尸进程"指什么,什么情况下会产生僵尸进程,如何杀掉僵尸 进程。 A: 在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之 前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽 略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是root身份kill -9 也不能杀死僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然 存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵 尸进程。 所谓以root身份也不能杀死僵尸进程,其实只是用户态的感觉。真正原因是这个 进程在内核看来已经结束了,你不能杀死一个死人,不是吗。内核维护了一些状 态信息,等待其父进程获取这些状态信息,除非父进程明确表示它不想获取这些 状态信息。 显式忽略SIGCHLD信号是指类似这样的代码: signal( SIGCHLD, SIG_IGN ); 安装SIGCHLD信号句柄是指类似这样的代码: static void on_sigchld ( int signo ) { pid_t pid; int status; while ( ( pid = waitpid( -1, &status, WNOHANG ) ) > 0 ) { /* * 演示用,不推荐在信号句柄中使用fprintf() */ fprintf( stderr, "child <%u> terminated", ( unsigned int )pid ); } /* end of while */ return; } /* end of on_sigchld */ ... ... signal( SIGCHLD, on_sigchld ); 当然,我们不建议使用signal(),应该使用sigaction()。 为了避免僵尸进程,有很多看似不同的办法,但其实质都是设法处理SIGCHLD信号。 APUE 8.6节最后一个例子演示了一种技巧,二次fork(),而不是安装SIGCHLD信号 句柄。这样做没有什么特别的好处,仅仅是演示另外一种处理思路。此外,显式 忽略SIGCHLD信号的作法并不具有最广泛可移植性,不建议采用。 for ( ; ; ) { pid = Fork(); if ( 0 == pid ) { /* * 子进程 */ pid = Fork(); if ( 0 == pid ) { ... ... } exit( EXIT_SUCCESS ); } else { /* * 父进程 * * 处理SIGCHLD信号,在此产生阻塞 */ if ( pid != waitpid( pid, NULL, 0 ) ) { perror( "waitpid error" ); } } } /* end of for */ 13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页 A: 小四 man -S 2 ptrace,下面是x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页 -------------------------------------------------------------------------- PTRACE(2) FreeBSD系统调用手册 PTRACE(2) 名字 ptrace - 进程跟踪和调试 库 标准C库(libc -lc) 摘要 #include #include int ptrace ( int request, pid_t pid, caddr_t addr, int data ); 描述 ptrace()提供了进程跟踪和调试能力,允许一个进程(the tracing process)控 制另一个进程(the traced process)。绝大多数时间被跟踪进程正常运行,但 当它接收到一个信号(参看sigaction(2))时,被跟踪进程停止运行。跟踪进程 应该调用wait(2)或者捕获SIGCHLD信号,以便意识到被跟踪进程运行状态的改 变,检查被跟踪进程返回的status,由跟踪进程作出适当反应(终止或继续被跟 踪进程)。ptrace()正是提供了这样的机制。 第2、3、4形参的意义依赖于第1形参request(后面会提到一个特殊例外情形)。 所有ptrace()调用均由跟踪进程完成,第2形参pid指定被跟踪进程。第1形参 request可以是 PT_TRACE_ME 唯一由被跟踪进程使用的request,指明该进程希望被它的父进 程跟踪。此时其它形参都被忽略。如果父进程并未准备好跟踪 子进程,比如没有调用wait(2)或者捕获SIGCHLD信号,就会导 致混乱;一旦子进程(被跟踪进程)停止,除非利用ptrace()机 制,否则它是不会再继续执行下去的。子进程使用这个request 并调用execve(2),在执行新映像的第1条指令之前,停止。 注意,execve(2)即将执行的可执行文件的setuid/setgid设置 此时被忽略。 PT_READ_I, PT_READ_D 这两个请求用于从被跟踪进程地址空间读取一个整型(int)数据。 历史上,某些架构有明确区分开来的指令空间和数据空间,所 以这里提供了两个request,PT_READ_I从指令空间读取, PT_READ_D从数据空间读取。在当前FreeBSD实现中,这两个请 求完全相同。 addr形参指定被跟踪进程虚拟地址空间中的一个地址,也就是 即将被读取的地址。addr不必考虑任何对齐约束。读取到的值 做为ptrace()的返回值返回。 PT_WRITE_I, PT_WRITE_D 与PT_READ_I 和 PT_READ_D类似,只不过这里是写。第4形参 data提供即将被写入的数据。 PT_READ_U 该request从被跟踪进程的U区(user structure)读取一个整型 数据。 形参addr指定一个U区内的偏移,对应即将被读取的整型数据。 通常为了与ptrace()函数原型匹配,形参addr会做到caddr_t的 强制类型转换,并包含相应的头文件。 与PT_READ_I 和 PT_READ_D不同,这次addr必须对齐在整型边 界上。读取到的值做为ptrace()的返回值返回。 PT_WRITE_U 这个request写一个int到被跟踪进程的U区。形参addr指定偏移, 虽然man手册没有明确指出addr需要对齐在int边界上,根据上 面 PT_READ_U 的要求,这里也是需要对齐的。类似PT_WRITE_I 和 PT_WRITE_D,形参data指定即将被写入的数据。 PT_CONTINUE 被跟踪进程继续执行。addr指定一地址,被跟踪进程从这个地 址恢复执行(一个新的PC值)。如果addr为( caddr_t )1,表示 从停止的地方恢复执行。形参data指定一个信号,当被跟踪进 程恢复执行后,该信号将分发给它。如果data为0,表示没有信 号需要分发。 PT_STEP 单步执行被跟踪进程一条机器指令。形参addr、data未用。 PT_KILL 终止被跟踪进程。类似 PT_CONTINUE 中指定了 SIGKILL 信号。 PT_ATTACH 这个request允许一个进程获取另外一个不相干进程(非父子进 程关系)的控制权,并开始跟踪它。第2形参pid指定即将被跟踪 的进程。addr和data被忽略。 要求被跟踪进程与跟踪进程拥有同样的RUID,被跟踪进程不能 具有setuid/setgid设置。如果跟踪进程以root身份启动,没有 这些限制。 使用 PT_ATTACH 之后,被跟踪进程将停止。然后跟踪进程可以 继续被跟踪进程,和父子进程关系下的跟踪并无区别。 PT_DETACH 类似 PT_CONTINUE ,不过这里形参addr无法任意指定。如果调 用成功,被跟踪进程与跟踪进程脱离关系,继续正常运行。 此外,存在一些架构相关的request,比如x86上有 PT_GETREGS 从被跟踪进程读取寄存器存放到形参addr指定的struct reg中, 参看 PT_SETREGS 与 PT_GETREGS 相对应,根据形参addr指定的struct reg设置 被跟踪进程的寄存器,参看 PT_GETFPREGS 从被跟踪进程读取浮点寄存器存放到形参addr指定的 struct freg中,参看 PT_SETFPREGS 与 PT_GETFPREGS 相对应,根据形参addr指定的struct freg设 置被跟踪进程的浮点寄存器,参看 PT_GETDBREGS 从被跟踪进程读取调试寄存器存放到形参addr指定的 struct dbreg中,参看 PT_SETDBREGS 与 PT_GETDBREGS 相对应,根据形参addr指定的struct dbreg 设置被跟踪进程的调试寄存器,参看 返回值 某些request导致ptrace()无错的时候也返回-1,为了消除歧义,考虑在 ptrace()调用之前设置errno成0,调用完成后检查errno是否还是0。 错误码 [ESRCH] o 指定PID并不对应一个运行中的进程 [EINVAL] o 一个进程企图针对自身使用 PT_ATTACH o 第1形参request无效 o PT_READ_U 或 PT_WRITE_U 中形参addr未对齐在int 边界上。 o PT_CONTINUE中形参data指定的信号既不是0也不是一 个合法信号。 o PT_GETREGS PT_SETREGS PT_GETFPREGS PT_SETFPREGS PT_GETDBREGS PT_SETDBREGS 企图作用在一个没有合 法(有效)寄存器组的进程上,通常只有系统进程是这 样的。 [EBUSY] o PT_ATTACH 企图作用在一个已经被跟踪的进程上 o 已经有进程A跟踪进程C,现在进程B企图针对进程C作 ptrace()操作。 o 企图对一个非停止状态的进程使用 PT_ATTACH 之外 的request [EPERM] o 企图对一个未被跟踪过的进程使用 PT_ATTACH 之外 的request o 使用 PT_ATTACH ,却又存在违背该request要求的现 象。 参看 execve(2) sigaction(2) wait(2) execv(3) i386_clr_watch(3) i386_set_watch(3) 历史 ptrace()源自Version 7 AT&T UNIX -------------------------------------------------------------------------- A: scz 下面来看一个简单的ptrace(2)演示,x86/FreeBSD 4.3-RELEASE -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o target target.c */ #include #include #include #include #include int main ( int argc, char * argv[] ) { write( STDERR_FILENO, "Hello world\n", 12 ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o ptracetest ptracetest.c */ #include #include #include #include #include #include #include #include #include int main ( int argc, char * argv[] ) { pid_t p; p = fork(); if ( p < 0 ) { perror( "fork error" ); exit( EXIT_FAILURE ); } else if ( p == 0 ) { /* * 子进程 */ errno = 0; ptrace( PT_TRACE_ME, 0, 0, 0 ); if ( errno != 0 ) { perror( "child process ptrace error" ); exit( EXIT_FAILURE ); } else { char * name[2]; name[0] = "./target"; name[1] = NULL; /* * 切换进程映像时停止执行 */ execve( name[0], name, NULL ); perror( "child process execve error" ); exit( EXIT_FAILURE ); } } else { /* * 父进程 */ fprintf( stderr, "Having a child process <%d>\n", ( int )p ); /* * 阻塞式waitpid() */ waitpid( p, NULL, 0 ); fprintf( stderr, "Now in parent process, " "please enter [CR] to continue ... ...\n" ); getchar(); errno = 0; ptrace( PT_CONTINUE, p, ( caddr_t )1, 0 ); if ( errno != 0 ) { perror( "parent process ptrace error" ); exit( EXIT_FAILURE ); } /* * 作为ptrace(2)演示,这里必须等待子进程先结束,否则由于父进程终止 * 而杀死子进程 */ fprintf( stderr, "Waiting the child process terminate ... ...\n" ); getchar(); } return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 13.10 如何知道哪个进程使用了哪个端口 Q: Solaris下netstat -na -P tcp告诉我哪些端口是打开的,但它没有报告是哪个进 程打开的。lsof可以满足我的需求,可我不想用lsof,它不是缺省安装的 A: Vitaly Filatov & scz 对于Solaris 8,可以使用这个演示脚本,如果不能满足你的需要,请自行修改 -------------------------------------------------------------------------- #! /bin/sh # find_socket_proc.sh for x86/SPARC Solaris 8 # # File : find_socket_proc.sh # Author : Vitaly Filatov # Fix : scz # Platform : x86/SPARC Solaris 8 # Create : 2001-10-28 00:32 # Modify : # PLATFORM="`uname -p`" if [ "${PLATFORM}" = "sparc" ] ; then PREFIX="" elif [ "${PLATFORM}" = "i386" ] ; then PREFIX="/usr" fi EGREP="${PREFIX}/bin/egrep" NAWK="${PREFIX}/bin/nawk" PFILES="/usr/proc/bin/pfiles" PS="${PREFIX}/bin/ps" SED="${PREFIX}/bin/sed" PROCLIST="`${PS} -ef | ${NAWK} 'NR > 1 {print $2}'`" for PID in ${PROCLIST} ; do if [ -n "`${PFILES} ${PID} 2>/dev/null | ${EGREP} S_IFSOCK`" ] ; then LINE_1="`${PS} -o pid,args -p ${PID} | ${NAWK} 'NR > 1 {print $0}'`" PORTLIST="`${PFILES} ${PID} 2>/dev/null | ${EGREP} 'sockname:' | \ ${SED} -e 's/.*port: \(.*\)/\1/g'`" for PORT in ${PORTLIST} ; do echo "${LINE_1} [${PORT}]" done fi done -------------------------------------------------------------------------- 如果你以普通用户身份运行,只能检查自己的进程,如果以root身份运行,可以检查 所有用户的进程。 pfiles没有lsof强大,比如: # lsof -i udp:32771 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME rpcbind 109 root 5u IPv4 0x30000675950 0t0 UDP *:32771 (Idle) # lsof -p 139 # pfiles 109 32771/UDP并未出现在pfiles输出中。此外,pfiles输出的sockname行无法区分是TCP 还是UDP,lsof则可以区分。 D: 2002-03-08 09:41 修订 1) Solaris 7 netstat -na -P tcp -f inet 2) FreeBSD 4.3-RELEASE netstat -s -p tcp 查香tcp协议的统计量 netstat -na | grep "^tcp4" 才能达到类似Solaris下netstat -na -P tcp的效果 3) FreeBSD 4.4-RELEASE netstat -na -p tcp效果类似于Solaris下netstat -na -P tcp 4) RedHat Linux 7.2 netstat -nap | grep "^tcp"直接可以看到端口所属的进程 netstat -na [] ={-t|--tcp} {-u|--udp} {-w|--raw} {-x|--unix} --ax25 --ipx --netrom A: x86/FreeBSD 4.3-RELEASE SOCKSTAT(1)手册页 2002-03-08 14:48 -------------------------------------------------------------------------- SOCKSTAT(1) FreeBSD常规命令手册 SOCKSTAT(1) 名字 sockstat - 列举打开的套接字 摘要 sockstat [-46clu] 描述 sockstat命令列举所以AF_INET、AF_UNIX(AF_LOCAL)套接字 -4 显示AF_INET (IPv4)套接字 -6 显示AF_INET6 (IPv6)套接字 -c 显示已建立联接的套接字 -l 显示尚处在LISTEN状态的套接字 -u 显示AF_LOCAL(Unix)套接字 如果没有指定-4、-6 或 -u三者中的任一个,sockstat将同时列举这三个域 如果没有指定-c 或 -l中的任一个,sockstat将同时列举它们 每个套接字列举信息如下 USER 拥有socket的用户 COMMAND 打开该socket的进程 PID 打开该socket的进程PID FD socket对应的文件描述符 PROTO 对AF_INET来说是该socket对应的传输层协议,对AF_UNIX来 说套接字类型((stream或datagram) ADDRESS 只AF_UNIX有这项,如果是bound socket,为该socket对应 的文件名。如果是其它socket,为对端(peer)对应的文件名、 PID以及文件描述符。如果这个socket既非bound也非 connected,这里显示"(none)" LOCAL ADDRESS 只AF_INET有这项。socket的本地IP、端口。参看 getsockname(2) FOREIGN ADDRESS 只AF_INET有这项。socket的对端IP、端口。参看 getpeername(2) 参看 fstat(1) netstat(1) inet(4) inet6(4) 历史 从FreeBSD 3.1开始提供该命令 作者 Dag-Erling Smorgrav -------------------------------------------------------------------------- 显然对于Linux有netstat -nap,对于FreeBSD有sockstat,至少这两种平台不再需要 lsof,或者不那么迫切需要lsof了。 A: 对于RedHat 7.2,有一个socklist(8),通过读取如下文件 /proc/*/fd/* /proc/net/tcp /proc/net/udp /proc/net/raw 同样可以获取socket与进程之间的联系信息。 13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数 Q: 谁能给我一段C代码,快速统计出一个指定用户所拥有的进程数。我想修改Apache 以阻止它超过kern.maxprocperuid限制后继续fork()产生新进程。如果Apache以 sudo方式启动,就可能出现这种情况。我该看ps(1)的源代码吗? A: Maxim Konovalov 参看src/usr.bin/killall/killall.c,这里用了sysctl()接口 A: Andrew 可以试试kvm_getprocs( KERN_PROC_UID ) 13.12 如何获取当前进程对应之静态映像文件的绝对路径 A: hushui110@SMTH 这是一个x86/Linux Kernel 2.4.7-10系统中利用proc获取绝对路径的例子 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -static -o myprog_2 myprog_2.c */ #include #include #include #define MAXBUFSIZE 1024 int main ( int argc, char * argv[] ) { char buf[ MAXBUFSIZE ]; int count; count = readlink( "/proc/self/exe", buf, MAXBUFSIZE ); if ( count < 0 || count >= MAXBUFSIZE ) { printf( "Failed\n" ); return( EXIT_FAILURE ); } buf[ count ] = '\0'; printf( "/proc/self/exe -> [%s]\n", 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_2 /proc/self/exe -> [/home/scz/src/myprog_2] [scz@ /home/scz/src]> ../src/myprog_2 /proc/self/exe -> [/home/scz/src/myprog_2] [scz@ /home/scz/src]> myprog_2 /proc/self/exe -> [/home/scz/src/myprog_2] [scz@ /home/scz/src]> 显然这里直接给出了最期待的结果,没有冗余信息。参看proc(5)手册页。 A: scz & microcat 2000-03-18 下面在x86/Linux Kernel 2.4.7-10上演示、讨论 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -static -o myprog myprog.c */ #include #include int main ( int argc, char * argv[] ) { return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- [scz@ /home/scz/src]> gcc -Wall -pipe -g -static -o myprog myprog.c [scz@ /home/scz/src]> echo $PATH /bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:. [scz@ /home/scz/src]> gdb ./myprog (gdb) b main (gdb) r (gdb) x/17s 0xbfffff00 0xbfffff00: "SHLVL=1" 0xbfffff08: "_=/bin/bash" 0xbfffff14: "SHELL=/bin/bash" 0xbfffff24: "HOSTTYPE=i386" 0xbfffff32: "OSTYPE=linux-gnu" 0xbfffff43: "HISTSIZE=1000" 0xbfffff51: "TERM=vt100" 0xbfffff5c: "HOME=/home/scz" 0xbfffff6b: "SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass" 0xbfffff9e: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:." 0xbfffffd2: "LESSCHARSET=latin1" 0xbfffffe5: "/home/scz/src/./myprog" <-- 注意这个输出 0xbffffffc: "" 0xbffffffd: "" 0xbffffffe: "" 0xbfffffff: "" 0xc0000000:
[scz@ /home/scz/src]> gdb myprog (gdb) b main (gdb) r (gdb) x/17s 0xbfffff00 0xbfffff00: "z" 0xbfffff02: "SHLVL=1" 0xbfffff0a: "_=/bin/bash" 0xbfffff16: "SHELL=/bin/bash" 0xbfffff26: "HOSTTYPE=i386" 0xbfffff34: "OSTYPE=linux-gnu" 0xbfffff45: "HISTSIZE=1000" 0xbfffff53: "TERM=vt100" 0xbfffff5e: "HOME=/home/scz" 0xbfffff6d: "SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass" 0xbfffffa0: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:." 0xbfffffd4: "LESSCHARSET=latin1" 0xbfffffe7: "/home/scz/src/myprog" <-- 注意这个输出 0xbffffffc: "" 0xbffffffd: "" 0xbffffffe: "" 0xbfffffff: "" [scz@ /home/scz/src]> gdb ../src/myprog (gdb) b main (gdb) r (gdb) x/17s 0xbfffff00 0xbfffff00: "=1" 0xbfffff03: "_=/bin/bash" 0xbfffff0f: "SHELL=/bin/bash" 0xbfffff1f: "HOSTTYPE=i386" 0xbfffff2d: "OSTYPE=linux-gnu" 0xbfffff3e: "HISTSIZE=1000" 0xbfffff4c: "TERM=vt100" 0xbfffff57: "HOME=/home/scz" 0xbfffff66: "SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass" 0xbfffff99: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:." 0xbfffffcd: "LESSCHARSET=latin1" 0xbfffffe0: "/home/scz/src/../src/myprog" <-- 注意这个输出 0xbffffffc: "" 0xbffffffd: "" 0xbffffffe: "" 0xbfffffff: "" 0xc0000000:
[scz@ /home/scz/src]> 这是ELF文件在Linux系统中加载进内存之后的布局简图 -------------------------------------------------------------------------- 0x08048000 code .text,代码,只读 data .data,包含已经初始化的数据,只读 bss .bss,未初始化数据,初始化成0,读/写 ... 堆区,动态分配获取的内存从.bss往内存高端增长 ... (heap),读/写 ... stack 栈区,起始地址大于0xBFFF0000 arguments main()的形参 environment 环境变量区域 program name execve()第一形参,不是argv[0] 0xBFFFFFFC null(dword) 最后四个字节固定为零 0xC0000000 -------------------------------------------------------------------------- 通常动态链接库被映射到0x40000000往高端的地址。对于Linux,如果打了不可执行 堆栈内核补丁,动态链接库被映射到0x40000000往低端的地址。tt说就是以前的映射 地址减去0x40000000。打了补丁后使得通过字符串拷贝(strcpy)传递shellcode相对 复杂化,需要更多技巧。 program name处不一定是绝对路径,实际对应了execve()第一形参。一般从shell上 启动进程,shell根据PATH环境变量自动搜索匹配出一个路径,未必是绝对路径。假 设PATH环境变量中有当前目录(.),所执行的程序也只在当前目录下有,直接指定程 序名(myprog)执行时,shell会向execve()第一形参传递"./myprog"。注意,execve 第一形参未必是绝对路径。用gdb加载后再执行,情况有所不同。如果gdb命令行上指 定的程序名非绝对路径,gdb在调用execve()之前会调用getcwd()拼接在程序名之前, 此时不依赖PATH环境变量。所以用gdb调试溢出程序时应该在命令行上指定绝对路径, 避免不必要的偏移调整。 现在来看这样一个演示程序 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -static -o myprog_1 myprog_1.c */ #include #include extern char **environ; int main ( int argc, char * argv[] ) { char *path = ( char * )( 0xc0000000 - 5 ); while ( *--path ); ++path; printf( "path --> %08x [%s]\n", ( unsigned int )path, path ); printf( "argv --> %08x\n", ( unsigned int )argv ); printf( "argv[0] --> %08x [%s]\n", ( unsigned int )argv[0], argv[0] ); printf( "environ --> %08x\n", ( unsigned int )environ ); printf( "environ[0] --> %08x [%s]\n", ( unsigned int )environ[0], environ[0] ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 直接执行,然后用gdb加载后执行,对比输出上的不同。 [scz@ /home/scz/src]> myprog_1 path --> bffffff1 [./myprog_1] <-- 注意这里前面多了一个"./" argv --> bffffb24 argv[0] --> bffffc29 [myprog_1] environ --> bffffb2c environ[0] --> bffffc32 [PWD=/home/scz/src] [scz@ /home/scz/src]> ./myprog_1 path --> bffffff1 [./myprog_1] argv --> bffffb24 argv[0] --> bffffc27 [./myprog_1] environ --> bffffb2c environ[0] --> bffffc32 [PWD=/home/scz/src] [scz@ /home/scz/src]> ../src/myprog_1 path --> bfffffec [../src/myprog_1] argv --> bffffb14 argv[0] --> bffffc18 [../src/myprog_1] environ --> bffffb1c environ[0] --> bffffc28 [PWD=/home/scz/src] [scz@ /home/scz/src]> Linux系统中main()函数里自修改argv[0]欺骗ps有效,而FreeBSD、Solaris则无效。 execve()第二形参可以指定不同于第一形参的argv[0]。argv[0]不可信,但execve() 第一形参相对就可信得多。如果是自己写程序,可以考虑在main()中第一时刻判断 execve()第一形参是否为相对路径,进而决定是否调用getcwd(),最后拼接出一个绝 对路径。就像gdb所做的那样。 需要考虑"/home/scz/src/./myprog"、"/home/scz/src/../src/myprog"这些情况, 消掉"./"、"../"等冗余信息,参看realpath()手册页。 A: scz & microcat 2002-09-13 18:00 下面在SARPC/Solaris 8 64-bit kernel mode上演示、讨论 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -static -o myprog_3 myprog_3.c */ #include #include extern char **environ; 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 */ int main ( int argc, char * argv[] ) { char *path = ( char * )( 0xffbf0000 - 5 ); while ( *path == '\0' ) { path--; } while ( *path ) { path--; } ++path; printf( "path --> %08x [%s]\n", ( unsigned int )path, path ); printf( "argv --> %08x\n", ( unsigned int )argv ); printf( "argv[0] --> %08x [%s]\n", ( unsigned int )argv[0], argv[0] ); printf( "environ --> %08x\n", ( unsigned int )environ ); printf( "environ[0] --> %08x [%s]\n", ( unsigned int )environ[0], environ[0] ); outputBinary( ( const unsigned char * )( 0xffbf0000 - 256 ), 256 ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 直接执行,然后用gdb加载后执行,对比输出上的不同。SPARC/Solaris与x86/Linux 在此问题上有不少区别。 [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>>、<>两篇QA。 D: law@SMTH 在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 2002-09-13 20:45 这是一个x86/FreeBSD 4.5-RELEASE系统中利用proc获取绝对路径的例子 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -static -o myprog_4 myprog_4.c */ #include #include #include #include #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//exe"一样,FreeBSD系统中"/proc//file"直接给 出了最期待的结果,没有冗余信息。参看procfs(5)手册页。 手头系统有限,无法一一验证其它Unix系统。个人觉得从栈底向低端移动取execve() 第一形参的办法比较通用,可以写成可移植的函数,但还是不太推荐这种Hacking。 13.13 x86/Linux Kernel 2.4.7-10的ptrace(2)手册页 A: 小四 2002-09-23 17:19 man 2 ptrace,下面是x86/Linux Kernel 2.4.7-10的ptrace(2)手册页 -------------------------------------------------------------------------- PTRACE(2) Linux程序员手册 PTRACE(2) 名字 ptrace - 进程跟踪 摘要 #include long int ptrace ( enum __ptrace_request request, pid_t pid, void *addr, void *data ); 描述 ptrace()系统调用提供了一种技术手段,使得一个进程可以观察、控制其它进程 的执行过程,检查或修改它们的进程映像、运行中的寄存器值。这个系统调用主 要用于实现断点调试以及跟踪系统调用。 有两种使用方式。一种是父进程调用fork(2),然后在子进程中指定形参 PTRACE_TRACEME调用ptrace(),接着调用exec*()启动最终被跟踪的程序。第二 种方式,指定形参PTRACE_ATTACH调用ptrace(),这种方式用于跟踪一个已经运 行中的进程。 如果子进程被跟踪了,每接收到一个信号就会中止运行,即使该信号在子进程中 被设置成SIG_IGN。有一个例外,SIGKILL信号依旧会杀掉子进程。父进程将在下 一次wait(2)调用发生时得到通知,于是可以观察、修改中止运行后的子进程。 父进程可以让子进程继续运行,比如忽略该信号,或者分发一个完全不同的信号 到子进程。 父进程完成跟踪之后,可以指定形参PTRACE_KILL调用ptrace()以终止子进程。 也可以指定形参PTRACE_DETACH调用ptrace(),使子进程脱离被跟踪状态,正常 地继续运行。 函数原型中request可取值如下: PTRACE_TRACEME 指明本进程被其父程跟踪。除SIGKILL之外的任意信号被分发到本进程都将 导致本进程中止运行,而其父进程在调用wait(2)时得到通知。此外,本进 程后续的exec*()调用将导致一个SIGTRAP信号分发到本进程,这给父进程一 个机会,可以在新进程开始执行前得到控制。如果父进程并不打算跟踪子进 程,子进程不应该指定形参PTRACE_TRACEME调用ptrace()。 指定形参PTRACE_TRACEME时,其余三个形参pid、addr以及data被忽略。 前面的request只由子进程使用,后面的request只由父进程使用。后续request 中,形参pid指定子进程的进程号。除了PTRACE_KILL之外的其它任意request, 都导致子进程中止运行。 PTRACE_PEEKTEXT, PTRACE_PEEKDATA 在子进程地址空间的addr处读取一个字(word)做为ptrace()调用的返回值。 由于Linux没有隔离text与data的地址空间,所以目前这两个requests等价。 形参data被忽略。 PTRACE_PEEKUSER 在子进程U区偏移addr处读取一个字(word)做为ptrace()调用的返回值。关 于进程U区请参看以及。一般addr位于一个字 (word)的自然边界上。形参data被忽略。 PTRACE_POKETEXT, PTRACE_POKEDATA 从父进程地址空间的data处复制一个字(word)到子进程地址空间的addr处。 同上,目前这两个requests等价。 译注: 至少在x86/Linux Kernel 2.4.7-10中man手册写错了,此时形参data 不是指针,而是强制类型转换后的(unsigned int)。换句话说,data 的值就是即将被写到子进程地址空间的数据。 PTRACE_POKEUSER 从父进程地址空间的data处复制一个字(word)到子进程U区偏移addr处。同 上,addr位于一个字(word)的自然边界上。为了维护内核完整性,不允许修 改子进程U区中某些部分。 译注: 同上,这里data不是指针,而是整型数据。 PTRACE_GETREGS, PTRACE_GETFPREGS 分别复制子进程通用寄存器、浮点寄存器值到父进程地址空间的data处。参 看了解data处的数据格式。形参addr被忽略。 PTRACE_SETREGS, PTRACE_SETFPREGS 根据父进程地址空间data处的数据分别设置子进程通用寄存器、浮点寄存器 值。与PTRACE_POKEUSER类似,不允许修改某些通用寄存器。形参addr被忽 略。 PTRACE_CONT 重新启动中止运行中的子进程。如果形参data既非零又非SIGSTOP,将被解 释成一个信号,最终分发到子进程。否则没有信号分发。这样,父进程可以 决定是否分发一个信号到子进程。形参addr被忽略。 译注: 这里是说形参data,而不是*data,应该有强制类型转换吧。 PTRACE_SYSCALL, PTRACE_SINGLESTEP 与PTRACE_CONT类似,重新启动中止运行中的子进程。不同之处在于,子进 程进入下一次系统调用时或者从一次系统调用退出时中止运行。 PTRACE_SINGLESTEP则指示子进程执行完一条汇编指令之后中止运行。从父 进程来看,子进程就像收到SIGTRAP信号一样中止运行。于是对于 PTRACE_SYSCALL,父进程可以查看子进程即将进入的系统调用所使用的形参。 接着父进程再次指定形参PTRACE_SYSCALL调用ptrace(),以查看子进程中相 应系统调用的返回值。形参addr被忽略。 PTRACE_KILL 向子进程分发一个SIGKILL信号以终止子进程。形参addr、data被忽略。 PTRACE_ATTACH 关联形参pid所指定的进程,使之进入被跟踪状态,而本进程进入跟踪状态。 被关联进程就像是主动指定形参PTRACE_TRACEME调用ptrace()一样。本进程 实际成为被关联进程的父进程,比如被关联进程终止时本进程可以收到 SIGCHLD信号,ps(1)输出中本进程也成为被关联进程的父进程。但是,在被 关联进程中调用getpid(2),返回值依然是原来的父进程pid。SIGSTOP信号 分发到子进程,但是在父进程看来子进程并未于ptrace()调用返回时立即中 止,父进程应该调用wait()等待子进程中止。形参addr和data被忽略。 PTRACE_DETACH 与PTRACE_CONT类似,重新启动中止运行中的子进程。但是首先去除子进程 与父进程的关联关系,消除父进程调用PTRACE_ATTACH或子进程调用 PTRACE_TRACEME的影响。在Linux系统中,无论最初以何种方式使子进程进 入被跟踪状态,都可以指定形参PTRACE_DETACH调用ptrace(),去除被跟踪 子进程与其跟踪父进程的关联关系。形参addr被忽略。 译注: 指定形参PTRACE_DETACH调用ptrace()时,pid有什么特别要求吗。比 如,A在跟踪B,那么C指定形参PTRACE_DETACH调用ptrace()时,pid 可以是B的pid吗。 既然有错误值ESRCH,上述问题的答案应该是否定的。(未测试) 注意 目前GNU libc将ptrace()声明成可变参函数,除了第一形参request固定之外, 其余形参属于变参。意味着被忽略的尾部形参可以省略不写,只是这样就依赖未 文档化的gcc(1)特性。 参看手册页init(8),该进程号为1,不能被跟踪。 内存布局、U区结构是操作系统相关、架构相关的。 字(word)的大小随操作系统不同而不同。比如32-bit Linux中,字的大小是32位。 如果一个进程被其它进程指定PTRACE_ATTACH关联上了,当其中止运行时,其原 有父进程得不到通知,新的父进程(跟踪进程)无法有效模拟出这个通知。 本份文档针对x86/Linux Kernel 2.4.7-10的ptrace(2)系统调用,很可能在不同 版本的Linux上有着显著差别。无论如何,ptrace(2)都是高度操作系统相关以及 架构相关的。 SunOS的手册页认为ptrace()是"独特的、不可思议的"。Solaris 2.x定义了基于 procfs的调试接口,其功能是ptrace()的超集,更加强大、统一。 返回值 成功时,PTRACE_PEEK*请求返回所请求的数据,其他请求返回0。 失败时,所有请求返回-1,全局变量errno(3)被设置成相应值。由于 PTRACE_PEEK*请求有可能成功返回-1,主调者必须在这类请求返回后检查全局变 量errno,以判断是否有错误发生。 错误值 EPERM pid对应的进程不能被跟踪。可能主调进程权限不够。非root权限的进程 不能跟踪那些它无权向之发送信号的进程,也不能跟踪使用了setuid、 setgid特性的进程。此外,可能pid对应的进程已经被跟踪,或者pid为1。 ESRCH 指定进程不存在,或者pid指定进程尚未被主调进程跟踪,或者request 要求pid指定进程中止运行中,而现下pid指定进程尚未中止运行。 EIO 无效请求,或者企图读写父子进程无效内存区域,或者违背了字(word) 对齐的要求,或者在一个重启子进程的请求中指定了无效信号。 EFAULT 试图读写父子进程无效内存区域,比如该内存区域尚未被映射(mmap)或 不可访问。不幸的是,在Linux系统中,此类错误有时返回EIO,有时返 回EFAULT,具体实现相关。 一致性 SVr4, SVID EXT, AT&T, X/OPEN, BSD 4.3 参看 exec(3), wait(2), signal(2), fork(2), gdb(1), strace(1) -------------------------------------------------------------------------- 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 rpm -qif `which ps`查到procps-2.0.7-11.src.rpm,在如下链接上找到 http://rpmfind.net/linux/RPM/redhat/7.2/i386/procps-2.0.7-11.i386.html ftp://ftp.redhat.com/pub/redhat/linux/7.2/en/os/i386/SRPMS/procps-2.0.7-11.src.rpm 从readproc.c的file2strvec()函数中可以看到"ps ax"在读取/proc//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//cmdline对应着argv[]数组。参看documentation/filesystems /proc.txt。 当读取/proc//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 #include #include #include #include #include #include #include #include #include #include /* * 这是操作系统相关、架构相关的栈底地址,参看<<如何编程获取栈底地址>> */ #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 []\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 [].\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 ps -p -o cmd ps -p -o command ps ax 对于这三种命令,linux_fakeps均有效。但是对于下面这三种命令,linux_fakeps无 效: ps -p -o comm ps axc ps 那是因为这三种情况下使用了进程P区的comm[16],不再是argv[]。 readproc() -> file2str()、stat2proc() -> show_a_proc() -> show_cmd_env() file2str()读取/proc//stat,最终就是读取了指定进程P区的comm[16]。 stat2proc()负责解析来自/proc//stat的数据。借助如下命令加强理解: # cat /proc/self/stat | od -Ax -tx1 -v - 现在从内核源码中进一步分析这个问题。当读取/proc//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 #undef __KERNEL__ #include #define OFFSETOF(TYPE, MEMBER) ((size_t)&((TYPE)0)->MEMBER) #define EXIT_FAILURE 1 /* Failing exit status */ #define EXIT_SUCCESS 0 /* Successful exit status */ /* * 由于包含了,无法正常包含这些函数原型所在头文件 */ 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 [].\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 []\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: 小四 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 #undef __KERNEL__ #include #define EXIT_FAILURE 1 /* Failing exit status */ #define EXIT_SUCCESS 0 /* Successful exit status */ /* * 由于包含了,无法正常包含这些函数原型所在头文件 */ 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 \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进制之间的转换 A: scz 2001-05-30 23:47 下列命令完成10进制65535到16进制0xFFFF的转换 echo c 65535 16o p c | /usr/bin/dc FFFF 下列命令完成16进制0x3934C024到10进制959758372的转换 echo c 16i 3934C024 p c | /usr/bin/dc 959758372 注意,0x3934C024中字母C必须是大写的,小写的就不是这个意思了。 同理,可以完成与8进制之间的转换,比如0xFF到8进制、10进制的转换 $ echo c 16i FF 8o p c | /usr/bin/dc 377 $ echo c 16i FF Ao p c | /usr/bin/dc 255 D: scz 2002-09-09 17:03 /usr/bin/dc可以在命令行上完成加减运算,注意大小写敏感。下面表示16进制输入、 16进制输出。由于16i在先,10o就已经按16进制输入对待了。 $ echo "c 16i 10o 9488 508 + p c" | /usr/bin/dc 9990 $ echo "c 16i 10o 20F 20 - p c" | /usr/bin/dc 1EF $ D: scz 2004-06-21 17:26 可用awk完成10进制到16进制的转换: $ echo 65534 | awk '{printf "0x%04X\n", $1;}' 0xFFFE 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 在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 "命令。 14.3 只在本地文件系统上查找 A: Sun Microsystems 2001-02-12 下面的脚本允许用户只在本地文件系统上查找文件,而不会跨越到NFS文件系统上去 -------------------------------------------------------------------------- #! /sbin/sh filename=$1 mountpoints=`df -F ufs | awk -F\( ' { print $1 } '` for mount in $mountpoints do find $mount -mount -name $filename done -------------------------------------------------------------------------- # findlocally vfstab /etc/vfstab /mnt/altroot/etc/vfstab # Q: 我不想在NFS AUTOFS CACHEFS这些文件系统上查找文件 A: Sun Microsystems 2001-02-12 下面举例演示如何不在NFS文件系统上查找文件: # find / -name su -type f -print -o -fstype nfs -prune 下例表示不在PROC文件系统中查找文件(参看/etc/vfstab文件) # find / -name su -type f -print -o -fstype proc -prune (scz: find / -name proc -prune -o -name su -type f -print) 下例表示不遍历nfs、autofs 和 cachefs 文件系统 # find / -name su -print -o -fstype nfs -prune -o -fstype autofs -prune \ -o -fstype cachefs -prune A: Sun Microsystems 1999-06-19 SGI Irix下命令"find / -local -print"的等价Solaris命令是 find / \! -local -prune -o -print 因为缺省"/etc/dfs/fstypes"文件只有"nfs"项,实际上述命令等价 find / -fstype nfs -prune -o -print 为了排斥autofs和cachefs,编辑/etc/dfs/fstypes文件内容如下 nfs NFS Utilities autofs AUTOFS Utilities cachefs CACHEFS Utilities D: scz 2001-10-26 20:48 更新 find / \! -local -prune -o -name su -print 为什么不能直接写成 find / -local -name su -print 看了find(1)手册页,没看出名堂来 -local /etc/dfs/fstypes文件中指定了远程文件系统类型,不在该文件中的文件系 统类型算本地文件系统类型。缺省情况下或/etc/dfs/fstypes文件不存在的 时候,只有nfs算远程文件系统类型。 # cat /etc/default/fs LOCAL=ufs # cat /etc/dfs/fstypes nfs NFS Utilities # 此外有个问题,这种用法什么意思啊 find /export/home \( -name scz -prune \) -o -type d -print \(和\)达到什么目的? Q: 想查出除/usr目录以外的所有SUID文件,Solaris系统 A: scz 2002-08-09 11:26 对于Solaris系统,其find不支持-path参数 find / \( -name usr -type d -prune \) -o -type f -perm -4000 -print 不过这个有点问题,会剪除类似/export/home/scz/usr这样的目录。 如果是FreeBSD/Linux系统,可能支持-path参数 find / \( -path "/usr" -prune \) -o -type f -perm -4000 -print 假设/usrscz/ls是SUID程序,上述命令会检查到其存在,也就是说-path "/usr"是精 确匹配的,FreeBSD、Linux俱如此。 find / \( -path "/usr*" -prune \) -o -type f -perm -4000 -print 这样就会剪除/usr/与/usrscz/两个目录。 注意使用-prune时与-print的配合,对比去掉-print时的输出。 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 非DDI/DKI兼容意味着丧失良好的可移植性,未来版本的Solaris可能不再提供相应的 支持,但是就Kernel Hacking而言是可以一试的。 adb -P 'sun>' -k /dev/ksyms /dev/mem 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 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 我并不认为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 2003-01-16 16:49 gcc生成程序时会添加sh_type为SHT_GNU_verdef的节,包含了glibc版本信息,以此 防止该程序与低版glibc搭配运行。 16.1 在Solaris 7下编写网络程序需要链接哪些库 Q: inet_pton()是什么库里的,为什么man手册里无对应内容 A: scz 这个函数比较新,还有另外几个,比如inet_ntop()。关于它们的详细介绍参看 <> 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/a_doc_lib/libs/basetrf1/exec.htm 现在有一个程序,需要一个正确的LD_LIBRARY_PATH环境变量设置才能运行,可是 由于某些原因必须做SUID设置,结果最终运行失败。我尝试在程序中putenv()、 setenv(),失败,显然LD_LIBRARY_PATH环境变量需要在程序加载过程中由动态链 接器使用,程序中的putenv()、setenv()为时已晚。 于是我写了一个脚本,在脚本中设置LD_LIBRARY_PATH环境变量,调用C程序,对 脚本做SUID设置。但是脚本的SUID设置并没有传递给子进程(这里就是那个C程序) A: Paul Sack 到www.google.com用"suid shell scripts race conditions"进行搜索,查看 BugTraq相关讨论。安全的解决办法是用C写一个SUID WRAPPER去exec...()你的C程序, 在SUID WRAPPER中设置LD_LIBRARY_PATH环境变量。下面这个举例未经测试 -------------------------------------------------------------------------- #include #include #include 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 我碰到过同样的问题,当时的解决办法是编辑/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 #include #include 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 如果一个程序是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 链接器按照命令行上指定顺序搜索库文件和目标文件(.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下如何构造动态链接库 A: Sun Microsystems 1998-03-26 编译时指定-KPIC选项导致编译器产生"位置无关"的目标文件 cc -c -KPIC file1.c cc -c -KPIC file2.c 然后经由编译器链接目标文件,指定-G和-o选项 cc -G -o libname.so.1 file1.o file2.o -G选项通知链接器生成动态链接库,-o指定即将创建的动态链接库名。".so"是 "shared object"的意思。 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 不要重启动,立即用/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 #include int main ( int argc, char * argv[] ) { char s[] = "Hello World\n"; printf( s ); return( EXIT_SUCCESS ); } -------------------------------------------------------------------------- -------------------------------------------------------------------------- /* mylib.c */ #include #include 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 ${CC} -g -G -o mylib.so mylib.c a.out: main.c mylib.so ${CC} -g main.c clean: 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 如何查看系统当前glibc版本 A: laws@SMTH 2004-08-18 11:13:55 $ alias glibcver="`ldd /bin/ls | cut -d' ' -f 3 | grep libc.so.`" $ glibcver GNU C Library stable release version 2.2.4, by Roland McGrath et al. Copyright (C) 1992-1999, 2000, 2001 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 2.96 20000731 (Red Hat Linux 7.2 2.96-108.1). Compiled on a Linux 2.4.9-31smp system on 2002-04-02. Available extensions: GNU libio by Per Bothner crypt add-on version 2.1 by Michael Glad and others The C stubs add-on version 2.1.2. linuxthreads-0.9 by Xavier Leroy BIND-8.2.3-T5B NIS(YP)/NIS+ NSS modules 0.19 by Thorsten Kukuk Glibc-2.0 compatibility add-on by Cristian Gafton libthread_db work sponsored by Alpha Processor Inc Report bugs using the `glibcbug' script to . $ 16.9 Solaris 8下如何配置运行时链接环境 Q: 在Linux下我知道用ldconfig(8)配置运行时链接环境,但是在Solaris 8下呢 A: 你总是可以利用 LD_LIBRARY_PATH 环境变量,对于Solaris 8,还可以参看crle(1) 手册页。 A: Logan Shaw 如果在链接时使用了"-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是什么软件包里的 Q: 今天编译ucd-snmp-4.2.3.tar.gz,SNMPv3开始支持加密。在做./configure时, 由于检测到openssl存在,导致加密支持被编译进去。但是ldd `which snmpget` 时,报告未找到libcrypto.so.0.9.6库,我该怎么办。SPARC/Solaris 7系统。 A: scz 2002-03-12 17:19 碰上这种事,应该用www.google.com指定相应关键字搜索,一般都有答案,这里还是 直接给出解决办法 wget http://www.openssl.org/source/openssl-0.9.6c.tar.gz gzip -cd openssl-0.9.6c.tar.gz | tar xvf - cd openssl-0.9.6c ./Configure --prefix=/usr/local --openssldir=/usr/local/ssl solaris-sparcv8-gcc make clean make make install strip /usr/local/bin/openssl make do_solaris-shared cp libcrypto.so.0.9.6 /usr/local/lib cp libssl.so.0.9.6 /usr/local/lib cd /usr/local/lib ln -sf libcrypto.so.0.9.6 libcrypto.so.0 ln -sf libcrypto.so.0.9.6 libcrypto.so ln -sf libssl.so.0.9.6 libssl.so.0 ln -sf libssl.so.0.9.6 libssl.so 现在整个世界都清净了。 16.11 共享库的动态加载/卸载 A: Mark Mitchell,Jeffrey Oldham,Alex Samuel 2002-08-07 22:43 这份文档来自<>,解释如何动态加载共享库,使用这 种技术你可以在程序中精确控制加载某个共享库。本文同时讨论了共享库中符号解析 问题。 dlopen( "libtest.so", RTLD_LAZY ); 这个调用将打开共享库libtest.so,第二形参通常都是RTLD_LAZY。为了使用dlopen 函数,需要包含头文件,指定链接开关-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 #include #include #include 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 #include #include #include #include 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 2003年5月31日 13:05 对于Win32、AIX来说,你所期待的效果是缺省行为,但其它操作系统未必如此。对于 Solaris、FreeBSD、Linux,使用gnu ld生成libfoo.so时应指定-Bsymbolic。缺省情 况下没有指定-Bsymbolic,此时prog.c中的func1()被func2()调用。 A: Mars Rullgard 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 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 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 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手册。 16.14 如何知道一个运行中的进程加载了哪些动态链接库 A: joe 2004-05-21 01:00 Solaris下可以试试pldd,Linux下则应查看/proc//maps A: Sony Antony 2004-05-21 01:11 平台无关的作法是"lsof -p " 16.15 制作so文件时如何指定引出哪些符号 Q: 制作so文件只想引出几个函数,其余函数不引出,就像Windows下dll一样。 A: jasss@SMTH 2005-05-16 一般来说至少有四种办法: 1) static 2) __attribute__ ((visibility("hidden"))) 3) Export Maps 4) libtool: -export-symbols A: Ulrich Drepper 2005-01-22 更详细的介绍参看下文: How To Write Shared Libraries http://people.redhat.com/drepper/dsohowto.pdf 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 下面几种方法都可以 /bin/nroff -man | more -s groff -Tlatin1 -mandoc | less groff -s -p -t -e -Tascii -mandoc | 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@SMTH Unix /usr/dt/bin/sdtimage *.ps 17.4 如何将man手册转换成文本文件以便查看 Q: "man sed > sed.txt"生成的sed.txt在Windows下用UltraEdit打开后并不可看, 含有控制字符。 A: 1997-07 man sed | col -b | sed -e "s/^H//g" -e "/^$/d" -e "s/^^I/ /g" -e "s/^I/ /g" > sed.txt 该命令删除所有退格键、空行,把行首的TAB替换成四个空格,其余TAB替换成一个空 格。注意^H和^I的输入方法是Ctrl-V/H(按住Ctrl不松,依次按V和H)和Ctrl-V/I。 18. 补丁相关问题 18.1 如何根据补丁号从Sun主站下载补丁 Q: 已经知道补丁号,可我如何从Sun主站下载这个补丁呢。有些补丁不是安全补丁, Sun公司并未在主站上提供非安全补丁的下载链接。 D: tt 2002-03-20 11:27 private-cgi下的需要一个帐号,可以直接这样,不需要帐号 wget -O 108869.tar.Z http://sunsolve.sun.com/pub-cgi/patchDownload.pl?target=108869&method=h 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命令时报错,无法卸载现有补丁。 D: scz 2004-03-16 16:45 一般安装完补丁后,查看如下目录与文件: /var/sadm/patch// (有一些说明、安装日志、卸载脚本) /var/sadm/pkg//save//undo.Z (卸载时所用备份文件) 18.3 patchdiag如何使用 18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁 A: scz # showrev -p | grep "^Patch: 105181" Patch: 105181-05 Obsoletes: 105636-01, 105776-01 Requires: ... # Q: 怎样看Solaris已经装了哪些patch A: tobago@SMTH 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 PATCHLIST",显示应用到指定包的所有补丁。 4) $ pkgparam SUNWsadmi PATCH_INFO_108869-15 Installed: Wed Mar 20 10:04:48 CST 2002 From: Sun8 Obsoletes: Requires: Incompatibles: $ 通用格式是"pkgparam PATCH_INFO_",显示何时从何 处安装了相应补丁到本机。 18.6 如何安装补丁 A: scz mkdir -p /tmp/patch cd /tmp/patch zcat 105181.tar.Z | tar xvf - zcat 106429.tar.Z | tar xvf - rm 105181.tar.Z rm 106429.tar.Z patchadd -M /tmp/patch 105181-28 106429-02 检查补丁安装日志 /var/sadm/patch/105181-28/log /var/sadm/patch/106429-02/log cd / rm -rf /tmp/patch # showrev -p | grep "^Patch: 105181" # init 6 (重启系统) D: scz 2003-02-28 17:38 在patchadd之前执行 $ showrev -p | grep "^Patch: 108376" Patch: 108376-38 Obsoletes: ... ... 看到旧版补丁号。如果下载了新版补丁包,可以直接patchadd安装,或者先卸载旧版 补丁包(不必要) # patchrm 108376-38 如果patchadd -d安装补丁,则无法使用patchrm卸载,注意这点。此外wget下载补丁 包后,应该用file命令确认文件格式,有可能需要unzip解压,而不是uncompress。 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 第一种方式不是总能达到目的。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: 小四 2002-04-25 16:47 2号句柄的此类安全问题在2002年4月23日得到了实际印证,可参看<>。 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在<> 中建议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"。 还可以修改/kernel/drv/options.conf文件,将"7f"替换成"8",比如 ttymodes="2502:1805:bd:8a3b:3:1c:7f:15:4:0:0:0:11:13:1a:19:12:f:17:16"; ttymodes="2502:1805:bd:8a3b:3:1c:8:15:4:0:0:0:11:13:1a:19:12:f:17:16"; 重启动后生效。该文件设置对应"/usr/bin/stty -g"的输出。 19.2 telnet时如何关闭本地回显 A: huxw PWin2K下 Ctrl-] unset LOCAL_ECHO XP下 Ctrl-] unset localecho D: 小四 2002-01-09 15:19 PWin98下执行telnet,在"终端"->"首选"里关闭"本地响应" Unix系统中telnet之后用Ctrl-]进入"telnet>"提示符,然后 telnet> status telnet> display echo telnet> send dont echo <-- Local character echo 此时将开启telnet的本地回显。如果要关闭本地回显就 telnet> send do echo <-- Remote character echo 这样就只有远程回显,没有本地回显了。 在测试telnet行输入模式时发现,从192.168.10.2(Solaris 7)登录 192.168.10.1(Linux),出现login提示后Ctrl-]输入 telnet> mode line 然后发现开始本地回显,口令输入过程也开始本地回显,登录成功后本地回显和远程 回显开始重叠出现,再次Ctrl-]输入 telnet> mode cha 立即解决了本地回显(应该是本地回显被关闭吧),无重叠显示现象。再次Ctrl-]输入 telnet> mode line 此时无重叠显示现象。看来登录时如果使用了mode line,会干扰telnet协商过程, 导致本地回显被打开。可以Ctrl-]输入 telnet> status telnet> display echo 验证当前是否打开了本地回显。以后碰上重叠显示现象,应该Ctrl-]输入 telnet> mode char 解决问题。进一步测试发现,在login处mode line切换,第一次仅仅导致本地回显被 打开,必须来第二次mode line切换,才能真正切换到行输入模式,此时还可以通过 send do echo关闭本地回显切回远程回显。远程回显+单字符输入模式配合比较好, 本地回显+行输入模式配合比较好。缺省情况下telnet使用单字符输入模式。 以前从www.gnuchina.org登录华中、木棉时,为了输入中文需要telnet -8,但是问 题发生在登录中,此时登录界面很混乱,而且输入bbs后居然开始提示口令,显然已 经无法正常登录。可以这样,telnet ip,登录进站后Ctrl-],输入toggle binary, 回车后效果和telnet -8一样了,可以输入中文,也解决了登录时的混帐问题,不清 楚登录时为何对-8反应这么大。 telnet> toggle binary 或者 telnet> set bin 19.3 如何清空stdin的缓冲 A: law@APUE stdin->_IO_read_ptr = stdin->_IO_read_end; 不过这个办法实在不怎么样。一是只对glibc有效,不可移植。二是违背流的思想, 老老实实用fgets()好了。 A: scz 2003-01-12 19:16 tcflush( STDIN_FILENO, TCIFLUSH ); D: law@SMTH 2005-05-31 17:15 tcflush()只能清空OS一级的缓冲,无法清空glibc里标准I/O一级(库函数级)的缓冲。 可以尝试: read( 0, buffer, size ); 不要用: getchar(); D: scz 2005-05-31 17:25 read()的话可能有一个无限阻塞的问题,要考虑动用select/poll()实现读超时。 19.4 Linux Console下一按错键就叫,怎么关 A: windtear@SMTH Linux版 有个1050110 背一下就可以了 echo -e "\\33[10;50]\\33[11;0]" 10 50 11 0 放到那些登录自启动脚本里 A: Sidos@SMTH 如果想在X下也没有这个声音,在/etc/inputrc里加上 set bell-style none Q: 输完命令后是没声了,可从KDE回来之后又有了,请问能彻底关掉吗 A: TheCool@SMTH 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 我们就作者所提供的原始代码进行了一些移植、修正,手头系统有限,未做更广泛的 可移植性测试。 -------------------------------------------------------------------------- /* * 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 #include #include #include #include #include #include #include #ifdef Solaris #include #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 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: 小四 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 #include #include #include 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输入。 19.7 如何让终端显示从黑方块状态恢复成正常状态 Q: 当时我在终端上执行了类似cat `which col`的命令,结果终端显示变成了黑方块状 态。嗯,我不清楚该如何表述这个状态,总之你试试就知道我的意思了。有人说可以 用stty sane或者stty crt恢复成正常状态,可我没有成功。如果从终端注销后重新 登录当然可以恢复正常,可我不想那样做,一定有别的办法吧。 A: 小四 1998 有一个非常邪门同时非常有效的办法,执行cat `which cal`即可。 20. shell script问题 20.0 不用临时文件完成字符串替换 Q: 将/tmp/target.txt中的/var/NSFocus/tmp替换成/var/tmp,要求在shell script 中完成,不使用临时文件 A: 小四 -------------------------------------------------------------------------- #! /bin/sh ed - /tmp/target.txt << EOF g/.*/s/\/var\/NSFocus\/tmp/\/var\/tmp/g w q EOF -------------------------------------------------------------------------- 这个可能更好些 -------------------------------------------------------------------------- #! /bin/sh ed - /tmp/target.txt << EOF # 第一个,相当于1,$ ,s,/var/NSFocus/tmp,/var/tmp,g w q EOF -------------------------------------------------------------------------- 20.1 如何获取一个字符串的长度 A: Andrei Ivanov 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 参看termio(7I)、stty(1)手册页了解更多关于"非规范模式"的信息,下面这个C程序 揭示了上述shell script的本质 -------------------------------------------------------------------------- /* * For x86/Linux、x86/FreeBSD、SPARC/Solaris * gcc -Wall -pipe -O3 -o keypress keypress.c */ #include #include #include #include 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: 小四 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 如何删除空行、空白符组成的行 A: starw@SMTH :g/^$/d A: althea@SMTH sed -e "/^[ \t]*$/d" srcfile > dstfile ^^^^^ 这里是一个空格和一个TAB字符 TAB可以用ctrl-V ctrl-I输入 如果在vi中,还可以(Linux下测试通过) :g/^[[:blank:]]*$/d D: Unknown x86/FreeBSD 4.3-RELEASE cat -s,不过不太通用,也仅仅是删除多余的邻近空行,只留一个。 A: stevensxiao cat -s不是每个平台都有,AIX上是cat -S,其他平台可能没有类似的选项。 -------------------------------------------------------------------------- #! /usr/bin/awk -f # squeeze.awk for x86/FreeBSD 4.3-RELEASE BEGIN { is_blank = 0 } /^$/ { if ( is_blank == 0 ) { print $0 } is_blank = 1 } !/^$/ { is_blank = 0 print $0 } -------------------------------------------------------------------------- chmod u+x squeeze.awk cat | ./squeeze.awk A: watercloud 2002-09-16 10:53 grep '\b' srcfile > dstfile 20.4 BASH中如何得到一个字符串的子串 A: loginlog@SMTH BASH 2.0.3 以上版本 ${var:offset:length} 20.5 shell script中如何关闭stdout A: 小四 2001-12-17 10:23 n<&- 关闭输入文件句柄n n>&- 关闭输出文件句柄n n>&m 复制句柄n成为输出句柄m的备份 n<&m 复制句柄n成为输入句柄m的备份 下面这个脚本在x86/Linux、x86/FreeBSD、SPARC/Solaris上测试通过 -------------------------------------------------------------------------- #! /bin/sh exec 1>/dev/null echo "scz" # 或者 exec 1>/dev/fd/2,但不能是 exec 1>/dev/fd/1 exec 1>/dev/fd/0 echo "hellguard" -------------------------------------------------------------------------- 下面这个脚本需要bash的支持,在x86/Linux、x86/FreeBSD、SPARC/Solaris上测试 通过 -------------------------------------------------------------------------- #! /bin/bash # 读写打开/dev/null获取一个文件句柄9 exec 9<>/dev/null # 复制句柄9成为句柄1的备份 exec 9>&1 # 关闭句柄1 exec 1>&- echo "scz" # 还原句柄1 exec 1>&9 # 关闭句柄9 exec 9>&- echo "hellguard" -------------------------------------------------------------------------- D: law@APUE 2001-12-19 15:58 用C程序来理解上述bash脚本 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o test test.c * * Only test for x86/Linux */ #include #include #include int main ( int argc, char * argv[] ) { FILE *file; int oldfd = dup( STDOUT_FILENO ); freopen( "/dev/null", "w", stdout ); printf( "hello\n" ); file = fdopen( oldfd, "w" ); /* * 下面这句在x86/FreeBSD、SPARC/Solaris上编译通不过,报告无效左值 */ stdout = file; printf( "world\n" ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- D: 小四 2001-12-19 16:10 针对law这个程序不能用于x86/FreeBSD、SPARC/Solaris,做如下妥协 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o test test.c * * Test for x86/FreeBSD、SPARC/Solaris */ #include #include #include int main ( int argc, char * argv[] ) { freopen( "/dev/null", "w", stdout ); printf( "hello\n" ); freopen( "/dev/fd/2", "w", stdout ); printf( "world\n" ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- D: scz 2002-08-06 13:08 -------------------------------------------------------------------------- #include static FILE *out = NULL; int main ( int argc, char * argv[] ) { ... ... out = stdout; ... ... } /* end of main */ -------------------------------------------------------------------------- 对于x86/FreeBSD 4.5-RELEASE和SPARC/Solaris 8,stdout是一个常量,在stdio.h 中定义,于是定义静态全局变量out时可以直接用stdout初始化。但是,对于 x86/Linux Kernel 2.4.7-10,stdout是一个外部变量,由glibc负责初始化stdout, 定义静态全局变量out时无法直接用stdout初始化。从可移植性出发,将out的初始化 移入main()函数。 20.6 如何将一个文本文件开始的N行删除 A: 小四 ${ed} - "${YOUR_FILE}" << EOF 1,${NUM}d w q EOF 或者 ${sed} -e "1,${NUM}d" ${YOUR_FILE} > "/tmp/${YOUR_FILE}.$$" ${mv} "/tmp/${YOUR_FILE}.$$" ${YOUR_FILE} 20.7 以字符串(非单个字符)为分隔的析取 Q: 假设有这样一行内容 "aaa: bbb: ccc:..." 想析取出aaa和第一个": "后面的部分,变成 "aaa" "bbb: ccc:..." "aaa"字符串中可能包含任意字符,比如":"或者空格,但是不会包含": "这个字 符串,如何实现 A: lookout@APUE -------------------------------------------------------------------------- #! /usr/bin/awk -f # # Test for x86/FreeBSD 4.3-RELEASE # # echo "L:ookout: abc: o k:smile" | awk -f scz.awk # BEGIN \ { FS = ": " } { print $1 for ( i = 2; i < NF; i++ ) { printf( "%s%s", $i, FS ); } print $i } END \ { } -------------------------------------------------------------------------- $ echo "L:ookout: abc: o k:smile" | awk -f scz.awk L:ookout abc: o k:smile $ D: 小四 在FreeBSD下还可以尝试 $ echo "L:ookout: abc: o k:smile" | sed -E -e 's/: /^A/' | tr '\1' '\n' L:ookout ~~ 注意这里没有g abc: o k:smile $ 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: 小四 先来看一个显示目录树的经典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 # Usage : ./csh_rm # Date : 2002-01-10 15:57 # # 必须指定第一形参 # if ( $#argv != 1 ) then echo "Usage: ${0} " exit 1 endif # # 第一形参必须是目录 # if ( ! -d ${1} ) then echo "Usage: ${0} " 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: 小四 如果要处理整个目录树的话,可以这样 find -exec sh -c 'mv -f "$0" `echo "$0" | tr "[A-Z]" "[a-z]"` > /dev/null 2>&1' {} \; 同理,将小写文件名转换为大写文件名如下 find -exec sh -c 'mv -f "$0" `echo "$0" | tr "[a-z]" "[A-Z]"` > /dev/null 2>&1' {} \; 这个办法有待修正,处理多层目录名本身带有大写字母的情况,有问题。比如存在如 下目录的时候,./A/B/C/D.txt。 A: Potash@www.linuxforum.net 2002-02-05 18:58 -------------------------------------------------------------------------- #! /bin/sh # Usage: ./loworup.sh <-l | -u> # # 第二形参必须是目录,第一形参指定-l或-u # if [ $# -ne 2 ] ; then echo "Usage: ${0} <-l | -u> " exit 1 fi if [ ! -d ${2} -o "${1}" != "-l" -a "${1}" != "-u" ] ; then echo "Usage: ${0} <-l | -u> " 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@SMTH cat << NONEXISTEOF > /dev/null ... ... NONEXISTEOF 或者 echo -n << NONEXISTEOF > /dev/null ... NONEXISTEOF 推荐使用echo法,这样开销小一些。 A: spec@APUE 2004-10-26 09:17 if [ 0 -eq 1 ]; then ... fi 20.13 批量文件字符串替换 A: scz -------------------------------------------------------------------------- #! /bin/sh # Usage: ./replace.sh if [ $# -ne 3 ] ; then echo "Usage: ${0} " exit 1 fi # # 第一形参必须是普通文件 # if [ ! -f ${1} ] ; then echo "Checking your " 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}" -------------------------------------------------------------------------- 20.14 如何显示当前目录下除./与../之外的所有文件、子目录名 A: scz 下述办法具有极大可移植性: ls -laF | egrep -v " ([.][.]|[.])/$" Linux的ls有了扩展,但不具有良好可移植性: ls -lAF 20.15 如何使nohup不输出 Q: 假设某程序的标准输出指向终端,当用nohup执行它时,stdout、stderr被自动转向 追加到./nohup.out尾部。如果不能写入./nohup.out,则使用$HOME/nohup.out,如 果仍不能写入,nohup执行失败。 有时需要长时间用nohup执行一个程序,而该程序stdout、stderr上的输出太多,担 心生成一个巨大的nohup.out文件,事实上并不需要查看这些输出,怎么办。 A: law@APUE 2004-11-30 在当前目录下建一个符号链接: ln -s /dev/null nohup.out 20.16 如何在csh的命令行提示符中包含当前路径信息 Q: 如果我用bash,可以在$HOME/.bash_profile中指定: PS1='[\u@ $PWD]> ' 如果我用csh,怎么才能达到类似效果? A: scz 1998-06 在$HOME/.cshrc中指定: alias cd 'cd \!*;set prompt = "[${user}@ ${cwd}]> "' alias pushd 'pushd \!*;set prompt = "[${user}@ ${cwd}]> "' alias popd 'popd \!*;set prompt = "[${user}@ ${cwd}]> "' set prompt = "[${user}@ ${cwd}]> " 执行下列命令使之立即生效: source $HOME/.cshrc 20.17 如果一行含有不以foo前导的bar,则满足需求 Q: 需要检查一行内容,当该行含有不以foo前导的bar,则满足需求。比如这些行满足需 求: bar prefix_bar bar_suffix prefix_bar_suffix foobar_infix_bar 而这些行不满足需求: foobar prefix_foobar foobar_suffix prefix_foobar_suffix 要求只用一条正则表达式完成,不得动用类似grep/egrep的"-v"这种外部力量。 A: backend 2005-09-21 考虑PCRE(Perl Compatible Regular Expressions): echo 'foobar_infix_bar' | grep -P '(?:^.{0,2}|(?!foo).{3})bar' echo 'foobar_infix_bar' | grep -P '(? 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: 袁哥 每个硬盘分区表项长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)手册页 Q: 以16进制单字节方式显示一个文件,类似Solaris下od -v -A x -t x1那种效果, 如何用hexdump -e实现之 D: backend hexdump -v -C | cut -d\| -f1 A: tt 2001-10-19 11:36 hexdump -v -e '"%07.7_ax " 16/1 "%02x " "\n"' /boot/mbr %07.7_ax _ax表示以16进制显示偏移,%07.7表示显示长度为7位,不足补零 16/1 表示显示16列,每列一个字节 %02x 表示显示长度为2,不足补零 注意format_string用单引号引起来了。 Q: 假设./test文件大小4338字节,试图以单行8进制显示成如下格式 \0177\0105\0114\0106\0001...(4338字节)... A: 小四 2003-02-20 17:21 $ wc -c ./test | awk '{print $1;}' 4338 $ hexdump -v -e '4338/1 "_%04o" "\n"' ./test | sed 's/_/\\/g' | more 21.5 x86/FreeBSD 4.3-RELEASE下DISKLABEL(8)手册页 21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel Q: 重新编译内核后用cp命令无法覆盖/kernel A: deepin # 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 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="" static_routes=" ..." route_="符合route add命令的语法格式" route_="符合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命令增加 路由。 A: x86/FreeBSD 4.3-RELEASE下ROUTE(8)手册页(部分 2001-10-25 17:27更新) 21.8 x86/FreeBSD 4.4-RELEASE下DIFF(1)手册页 A: x86/FreeBSD 4.4-RELEASE下DIFF(1)手册页(部分 2001-11-06 19:59) -------------------------------------------------------------------------- DIFF(1) GNU工具 DIFF(1) 名字 diff - 比较两个文件的不同 摘要 diff [options] from-file to-file 描述 文件名"-"代表标准输入 如果from-file是一个目录,to-file不是,diff将在from-file中寻找与 to-file同名的文件进行比较,反之亦然。非目录文件必须不是"-" 如果from-file和to-file都是目录,diff将在两个目录中依字母顺序比较相 应文件,如果指定了-r参数才进行递归比较。diff永远不将一个目录当做普 通文件而比较它的内容。涉及目录时不应该出现"-"。 diff options以'-'开始,所以一般情况下from-file和to-file不以'-'开头, 然而,可用"--"表示options的终结,从而其后的文件名可以'-'开头。 Options 下面是GNU diff接受的所有options。无参数options可以合并,比如-a -c可 以合并成-ac。[]表示可选。 --text -a 将所有文件当做文本文件进行逐行比较,即使它们不是文本文件 --ignore-all-space -w 行比较时忽略行内所有white space,与-b有区别,参看后面的举例 --ignore-space-change -b 行比较时忽略行内white space数量上(至少有1)的差别 --ignore-case -i 忽略大小写的区别,认为是相同的 --ignore-blank-lines -B 行比较时忽略 blank lines,这里blank lines指单个\n,含空格、 TAB的行不是空行 --ignore-matching-lines=regexp -I regexp regexp指定正则表达式,行比较时忽略正则表达式匹配行 --brief 仅仅报告文件是否不同,而不报告差别细节 --report-identical-files -s 如果两个被比较文件相同,报告一条信息。如果比较两个二进制文件, 最好diff -s -q 仅报告两个文件是否不同,不报告细节 -o 默认的传统输出格式 -u unified 输出格式 -c context 输出格式 --initial-tab -T 在context输出格式中每行前面使用TAB,而不是空格 --context[=num] -C [num] context 输出格式,显示num(一个整数)这么多行的上下文(前后行), 如果没有指定num,缺省为3。patch命令至少需要两行的上下文。 --unified[=num] -U num unified 输出格式,显示num(一个整数)这么多行的上下文(前后行), 如果没有指定num,缺省为3。patch命令至少需要两行的上下文。 --side-by-side -y side by side 输出格式 建议不要和那些指定"行忽略"的选项(比如-B、-I)一起使用-y、-c --width=columns -W columns 指定 side by side 输出格式时使用的列宽度,与-y一起使用 -e 产生一个相应的ed脚本 --speed-large-files -H 当两个大文件之间有众多分散的小差别时,使用该选项可以加速比较 过程 --minimal -d 改变比较算法试图找出更细微的变化,此时比较速度很慢 --recursive -r 涉及目录比较时,进行子目录递归比较 --exclude=pattern -x pattern 涉及目录比较时,忽略那些basename匹配pattern的文件和子目录 --exclude-from=file -X file 类似-x pattern,在file中指定多个pattern --version -v 版本号 --expand-tabs -t 在输出中将TAB扩展成空格 --label=label -L label 在context和unified输出格式头部使用label,而不是文件名 --left-column 在side by side输出格式中如果两列相同,则只显示左边一列 --new-file -N 在目录比较中,如果一个文件仅在其中一个目录中发现,则当其在另 外一个目录中也存在,但内容为空 --unidirectional-new-file -P 在目录比较中,如果一个文件仅在第二个目录中发现,当其在第一个 目录中也存在,但内容为空 --starting-file=file -S file 在目录比较中,从指定文件开始比较。这个选项用于断点续比 --show-c-function 显示其中有变化的C函数 --suppress-common-lines 在side by side输出格式中只显示不同行 环境变量 环境变量DIFF_OPTIONS指定一组diff使用的缺省选项,可能被命令行参数所 覆盖 举例 diff -crN foo.orig foo >foo.diff 这里foo、foo.orig是两个源代码树目录 参看 cmp(1) comm(1) diff3(1) ed(1) patch(1) pr(1) sdiff(1) 诊断 退出码0意味着完全相同,1意味着找到一些不同,2意味着麻烦来了 -------------------------------------------------------------------------- D: scz 2001-11-07 09:39 例1 $ cat 1.txt aa bbb cccc ddddd eeeeee $ cat 2.txt aa bbb cc c cccc ddddd ee ee $ diff 1.txt 2.txt <-- 默认输出格式 2a3 > cc c 5c6 < eeeeee --- > ee ee $ diff -u 1.txt 2.txt <-- unified 输出格式 --- 1.txt Wed Nov 7 11:06:43 2001 +++ 2.txt Wed Nov 7 11:10:50 2001 @@ -1,5 +1,6 @@ aa bbb +cc c cccc ddddd -eeeeee +ee ee $ diff -u -L label1 1.txt -L label2 2.txt --- label1 +++ label2 @@ -1,5 +1,6 @@ aa bbb +cc c cccc ddddd -eeeeee +ee ee $ diff -c 1.txt 2.txt <-- context 输出格式 *** 1.txt Wed Nov 7 11:06:43 2001 --- 2.txt Wed Nov 7 11:10:50 2001 *************** *** 1,5 **** aa bbb cccc ddddd ! eeeeee --- 1,6 ---- aa bbb + cc c cccc ddddd ! ee ee $ diff --initial-tab -c 1.txt 2.txt *** 1.txt Wed Nov 7 11:06:43 2001 --- 2.txt Wed Nov 7 11:10:50 2001 *************** *** 1,5 **** aa bbb cccc ddddd ! eeeeee --- 1,6 ---- aa bbb + cc c cccc ddddd ! ee ee $ diff -y 1.txt 2.txt <-- side by side 输出格式 aa aa bbb bbb > cc c cccc cccc ddddd ddddd eeeeee | ee ee $ diff -y -W 14 1.txt 2.txt <-- -W指定总宽度 aa aa bbb bbb > cc c cccc cccc ddddd ddddd eeeee | ee e $ diff --left-column -W 14 -y 1.txt 2.txt aa ( bbb ( > cc c cccc ( ddddd ( eeeee | ee e $ diff --suppress-common-lines -W 14 -y 1.txt 2.txt > cc c eeeee | ee e $ diff -I '^[ce]' 1.txt 2.txt;echo $? <-- 行比较时忽略以c、e开头的行 0 $ diff -e 1.txt 2.txt <-- 产生一个有效的ed脚本 5c ee ee . 2a cc c . $ 例2 $ cat 1.txt aa bbb cccc ddddd $ cat 2.txt aa bbb c ccc dd ddd $ diff 1.txt 2.txt 3,4c3,4 < cccc < ddddd --- > c ccc > dd ddd $ diff -yb 1.txt 2.txt <-- 与--ignore-all-space不同 aa aa bbb bbb cccc | c ccc ddddd | dd ddd $ diff -w 1.txt 2.txt $ diff --ignore-all-space -y 1.txt 2.txt <-- 忽略空格的比较,与-b不同 aa aa bbb bbb cccc c ccc ddddd dd ddd $ diff --brief 1.txt 2.txt Files 1.txt and 2.txt differ $ diff --ignore-all-space --brief 1.txt 2.txt $ 例3 $ cat 1.txt aa bbb c ccc dddd d $ cat 2.txt aa bbb c ccc dddd d $ diff -yb 1.txt 2.txt <-- 忽略空格数量上的区别,至少为1 aa aa bbb bbb c ccc c ccc dddd d dddd d $ 例4 $ cat 1.txt aa bbb c ccc ddddd $ cat 2.txt aa bbb c ccc ddddd $ diff 1.txt 2.txt 4d3 < $ diff -y 1.txt 2.txt aa aa bbb bbb c ccc c ccc < ddddd ddddd $ diff -B 1.txt 2.txt <-- 忽略空行进行比较,此时无差别 Q: 如何生成patch命令可用补丁 A: 不点@www.linuxforum.net diff -Naur old_file new_file > your_diff_file old_file 和 new_file 可以是两个目录 old_file 和 new_file 最好处于当前目录之下,前面不要冠以路径前缀 patch的步骤如下: cd your_dir ls -ld old_file patch -p0 < your_diff_file 也可能是: patch -p1 < your_diff_file 这样old_file就变成新的内容了,man patch看看说明 21.9 什么是locale A: Shen Chuan-Hsing 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 A: deepin & scz 2001-11-20 09:42 0) vim主站在http://www.vim.org/ 1) # which cvsup /usr/local/bin/cvsup 如果没有,就用www.google.com去搜一个好了,以"cvsup tgz"做关键字 # wget http://people.freebsd.org/~jdp/s1g/i386-nogui/cvsup-16.1e.tgz # pkg_add cvsup-16.1e.tgz 2) # cd /usr/share/examples/cvsup # cp ports-supfile scz # vi scz # cvsup -g -L 2 scz -------------------------------------------------------------------------- # # cvsup配置文件 # *default host=cvsup.cn.FreeBSD.org *default base=/usr *default prefix=/usr *default release=cvs tag=. *default delete use-rel-suffix *default compress #ports-all ports-editors -------------------------------------------------------------------------- 3) # cd /usr/ports/editors/vim # make -D WITHOUT_X11 install <-- 否则必须在X下使用vim # whic vim /usr/local/bin/vim <-- vim直接支持输入中文 21.11 FreeBSD下显示、输入中文 A: Shen Chuan-Hsing 本文来自<>,参看如下链接 http://freebsd.sinica.edu.tw/~statue/zh-tut/ 这通常都是设定了LC_CTYPE为zh_TW.Big5(对大陆是zh_CN.EUC)或是没设定LC_CTYPE 才会发生的问题,在~/.cshrc中加上下面的alias即可: alias vi 'env LC_CTYPE=en_US.ISO_8859-1 vi' 参看PRINTENV(1)手册页了解更多env命令细节。直接改用vim也可以支持中文。 D: scz 2001-11-20 10:39 $ LANG=zh_CN.EUC;export LANG $ LC_ALL=zh_CN.EUC;export LC_ALL 这两个环境变量设置对vi中使用汉字无意义。对于bash用户可以编辑/etc/profile alias vi='env LC_CTYPE=en_US.ISO_8859-1 vi' A: lgwu 2002-03-12 13:53 让FreeBSD默认的vi能显示、输入中文,命令行能输入中文,more、cat都能显示中文 在/etc/profile中增加如下行 LANG=it_IT.ISO_8859-1;export LANG MM_CHARSET=ISO-8859-1;export MM_CHARSET LC_ALL=en_US.ISO_8859-1;export LC_ALL x86/FreeBSD 4.3-RELEASE上测试成功。注意,如果是csh,不使用/etc/profile文件, 可以放在$HOME/.cshrc中 setenv LANG it_IT.ISO_8859-1 setenv MM_CHARSET ISO-8859-1 setenv LC_ALL en_US.ISO_8859-1 D: huxw 这个似乎不成功,建议LANG和LC_ALL还是设为zh_CN.EUC的好,MM_CHARSET不知是干 什么的。more和less加一个参数-r就可以显示中文了。不行的话还可以试试 stty cs8 -istrip stty pass8 D: scz huxw不成功可能是在console上吧,我是在远程telnet,client是Pwin98 21.12 如何在OpenSSH中限制只允许某些用户登录 A: tt 2001-11-20 10:56 编辑/etc/ssh/sshd_config,在最后增加下列语句 AllowUsers support 这将只允许"support"帐号通过ssh登录系统,如果需要增加其他用户,可以使用空格 隔开。支持*、?通配符。 对于root帐号,单独有一个配置选项 PermitRootLogin yes 21.13 在FreeBSD 4.3-RELEASE上安装libpcap、libnet A: 小四 2001-11-27 14:51 1) 最快的办法是www.google.com,不过这里还是提供两个下载链接 http://www.tcpdump.org/release/libpcap-0.6.2.tar.gz http://www.packetfactory.net/libnet/dist/libnet.tar.gz 2) tar xvfz libpcap-0.6.2.tar.gz cd libpcap-0.6.2 ./configure <-- 这里会生成一个Makefile, 可以vi查看这个Makefile make CC=gcc prefix=/usr clean depend all <-- 缺省是/usr/local make prefix=/usr install ls -l /usr/include/pcap.h ls -l /usr/lib/libpcap.a 很奇怪的问题,为什么这里只有静态库libpcap.a,而没有动态库libpcap.so?我 指定了prefix为/usr,而不是缺省的/usr/local,主要是考虑将来编程方便些, 不想指定LD_LIBRARY_PATH、-I、-L等等。 3) tar xvfz libnet.tar.gz cd Libnet-1.0.2a ./configure make clean all make install { make CC=gcc prefix=/usr clean all make prefix=/usr install } ls -l /usr/lib/libnet.a ls -l /usr/include/libnet.h 4) 后面的演示程序用到了getopt_long(),FreeBSD缺省不支持它,利用cvsup可获取 这个支持 cd /usr/share/examples/cvsup cp ports-supfile scz vi scz cvsup -g -L 2 scz -------------------------------------------------------------------------- # # cvsup配置文件 # *default host=cvsup.cn.FreeBSD.org *default base=/usr *default prefix=/usr *default release=cvs tag=. *default delete use-rel-suffix *default compress #ports-all ports-devel -------------------------------------------------------------------------- cd /usr/ports/devel/libgnugetopt make deinstall clean make PREFIX=/usr install <-- 缺省是/usr/local ls -l /usr/lib/libgnugetopt.* ls -l /usr/include/getopt.h <-- 缺省没有这个头文件 21.14 如何使自己的BMP图象成为启动logo 21.15 UDMA ICRC error是什么意思 Q: 在console上出现错误信息"UDMA ICRC error writing... ...",什么意思 A: tt 通常是使用了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 某些主机快速访问你的主机上一些没有开放的端口,你的主机正在回复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 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: 小四 2002-01-09 13:46 1) 启动时加载/kernel前的10s暂停如何调整 autoboot_delay="10" # Delay in seconds before autobooting 21.19 FreeBSD中sysctl可控内核参数 D: 小四 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)手册页 A: x86/FreeBSD 4.3-RELEASE下GETIFADDRS(3)手册页 2002-01-30 17:29 -------------------------------------------------------------------------- GETIFADDRS(3) FreeBSD库函数手册 GETIFADDRS(3) 名字 getifaddrs - 获得网络接口地址 摘要 #include #include #include int getifaddrs ( struct ifaddrs **ifap ); void freeifaddrs ( struct ifaddrs *ifp ); 描述 getifaddrs()会返回一个( struct ifaddrs * )型的指针,这个指针本身保存 在第一形参ifap指向的变量中,所以在调用getifaddrs()前,必须确保第一形 参ifap指向的变量( *ifap )确实存在。 *ifap指向一个本机网络接口链表,结点结构定义在/usr/include/ifaddrs.h文 件中。 struct ifaddrs { struct ifaddrs *ifa_next; /* Pointer to next struct */ char *ifa_name; /* Interface name */ u_int ifa_flags; /* Interface flags */ struct sockaddr *ifa_addr; /* Interface address */ struct sockaddr *ifa_netmask; /* Interface netmask */ struct sockaddr *ifa_dstaddr; /* P2P interface destination */ void *ifa_data; /* Address specific data */ }; /* * This may have been defined in . Note that if * is to be included it must be included before this * header file. */ #ifndef ifa_broadaddr #define ifa_broadaddr ifa_dstaddr /* Interface broadcast address */ #endif 这个结构可能在中也有定义,如果包含了,则它必须出 现在之前。 ifa_next指向下一个结点,最后结点中该域为NULL。 ifa_name域包含网络接口名 ifa_flags域包含interface flags,参看ifconfig(8)手册页 如果本接口对应一个地址或一个链路层地址,ifa_addr指向它,否则该域为NULL。 应该参看( ifa_addr->sa_family )成员,以确定( *ifa_addr )的格式。 如果( *ifa_addr )有相应的子网掩码,ifa_netmask指向它,否则为NULL 只有non-P2P接口才有ifa_broadaddr域,如果( *ifa_addr )有相应的广播地址, ifa_broadaddr指向它,否则为NULL 对于P2P接口,如果存在对端地址,ifa_dstaddr指向它,否则为NULL ifa_data指向地址族相关的数据。对于AF_LINK地址族,ifa_data指向 struct if_data(在中定义),包含各种各样的接口属性、统计量。 对于其它地址族,ifa_data指向struct ifa_data(在中定义),提供 每个地址的接口统计量 注意,getifaddrs()隐式动态分配了内存,所返回的数据如果不再需要了,应 该调用freeifaddrs()释放掉。 返回值 0 成功 -1 失败,同时设置errno 错误 如果getifaddrs()调用失败,errno可能值很多,参看ioctl(2)、socket(2)、 malloc(3) 或 sysctl(3) BUGS 如果同时包含了,则前者必须先包含。 参看 ioctl(2)、socket(2)、sysctl(3)、networking(4)、ifconfig(8) 历史 getifaddrs()最早在 BSDI BSD/OS 中实现 -------------------------------------------------------------------------- 21.21 FreeBSD下如何访问显存 Q: 我在开发一个FreeBSD上的内核模块,需要读写VGA文本模式屏幕缓冲区,也就是物理 地址0xB8000。如果在MS-DOS下,当然可以直接访问这个地址,但现在我在FreeBSD内 核中,必须使用内核虚拟地址,我的问题是如何"正确地"转换物理地址到内核虚拟地 址。目前我用了如下技巧,0xC0000000 + 0xB8000,但这是不规范的。 A: "M. Warner Losh" 1) bus_alloc_resource() 2) bus_space_{read,write}_* D: 小四 应该也可以用mmap()通过/dev/mem映射0xB8000吧,参看"直接访问内存[显存]地址" 21.22 FreeBSD下如何为指定用户设定chroot的FTP环境 A: backend 2002-03-19 11:13 将该用户帐号添加到/etc/ftpchroot文件中,则该帐号FTP登录后被限制在其登录目 录下。如果希望限制用户组,则在组名前添加@字符并写入/etc/ftpchroot文件中。 修改后立即生效,不需要重启inetd进程。 # cat /etc/ftpchroot badguy @badgroup # 21.23 如何利用FKLD动态增加一个新协议 Q: Marco Molteni 在netinet/in_proto.c中有一个结构struct ipprotosw inetsw[],包含了各种IP协 议的入口。如果想增加一个新协议,就必须在这个结构数组中增加入口。我想知道能 否利用FKLD方式增加一个新协议 A: Brooks Davis 看看sys/net/if_gif.c,这里增加了一个新的IP协议,而且是可加载的。 21.24 修改/etc/mail/sendmail.cf关闭ident功能 Q: FreeBSD系统,本地/远程使用sendmail发信时缓慢,但TCP连接建立正常,为何 A: backend 用telnet连接25端口,发现在TCP连接建立后,第一条提示信息需要等待几秒才能出 现。利用netstat命令查看后发现sendmail进程在此之前连接客户端的113端口(identd ),基本确定是这个查询客户端用户的操作产生延迟。修改/etc/mail/sendmail.cf配 置文件,关闭ident(RFC 912/931/1413)功能。 #O Timeout.ident=5s O Timeout.ident=0s 21.25 FreeBSD下如何获取系统负载 A: Anthony Schneider 可以用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 Q: 这里是一台刚安装完毕的FreeBSD 4.5,我想使用BASH,怎么办 A: 1) wget http://people.freebsd.org/~jdp/s1g/i386-nogui/cvsup-16.1e.tgz pkg_add cvsup-16.1e.tgz ls /usr/local/bin/cvsup 2) cd /usr/share/examples/cvsup cp ports-supfile scz vi scz cvsup -g -L 2 scz -------------------------------------------------------------------------- # # cvsup配置文件 # *default host=cvsup.cn.FreeBSD.org *default base=/usr *default prefix=/usr *default release=cvs tag=. *default delete use-rel-suffix *default compress #ports-all #ports-ftp #ports-net ports-shells -------------------------------------------------------------------------- 3) cd /usr/ports/shells/bash2 make clean <-- 这里可以考虑make deinstall make PREFIX=/usr/local install ls -l /usr/local/bin/bash 4) vipw命令修改登录shell D: scz 2002-04-23 21:37 cvsup本身也是ports的一部分,位于/usr/ports/net/cvsup,对应ports-net。wget 位于/usr/ports/ftp/wget,对应ports-ftp。最开始可以在Windows下找到cvsup和 wget的二进制安装包,ftp上去,pkg_add安装使用。然后再利用cvsup机制升级。 cvsup安装过程会将源代码包先下载到/usr/ports/distfiles/目录中,如果cvsup使 用的下载点连接困难,可以自行下载源代码包并放到该目录中,然后make install。 D: scz 2002-07-19 12:45 1) 安装cvsup cd /usr/ports/net/cvsup make -D WITHOUT_X11 make -D WITHOUT_X11 install 直接make太痛苦了,耗时如此之长,主要是带了X Window支持。 2) 更新ports cd /usr/share/examples/cvsup vi scz cvsup -g -L 2 scz -------------------------------------------------------------------------- # # cvsup配置文件 # *default host=cvsup.cn.FreeBSD.org *default base=/usr *default prefix=/usr *default release=cvs tag=. *default delete use-rel-suffix *default compress #ports-all #ports-net ports-converters ports-devel ports-ftp ports-shells ports-textproc -------------------------------------------------------------------------- 3) 安装wget cd /usr/ports/converters/libiconv make clean deinstall install cd /usr/ports/devel/bison make install cd /usr/ports/textproc/expat2 make install cd /usr/ports/devel/gettext make install cd /usr/ports/ftp/wget make PREFIX=/usr install 4) 安装bash cd /usr/ports/shells/bash2 make clean deinstall make PREFIX=/usr install ls -l /usr/bin/bash vipw vi /etc/profile PS1='[\u@ $PWD]> ' alias ls='ls -aF' export TERM=vt100 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 试试 sysctl kern.sugid_coredump=1 (缺省为0) 如果ftpd崩溃发生在用户登录完成之后,UID != EUID,必须做如上指定。遗憾的是, 这个设置并未解决前面的问题,不知还有什么地方限制了core dump产生? D: Garrett Wollman 设计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 Q: 我装了一个OpenBSD 3.0,却发现没有安装Gcc,/usr/bin/gcc不存在,怎么办 A: 小四 2002-05-20 13:24 1) 安装gcc 用安装光盘,选"Upgrade",选中comp30.tgz,其它包可以去掉。此时不影响以前在 /etc/下的配置文件。 2) 安装wget pkg_add -v ftp://www.openbsd.org/pub/OpenBSD/3.0/packages/i386/libiconv-1.7.tgz pkg_add -v ftp://www.openbsd.org/pub/OpenBSD/3.0/packages/i386/gettext-0.10.40.tgz pkg_add -v ftp://www.openbsd.org/pub/OpenBSD/3.0/packages/i386/wget-1.7.tgz 3) 安装bash的静态版本 pkg_add -v ftp://www.openbsd.org/pub/OpenBSD/3.0/packages/i386/bash-2.05-static.tgz vi /etc/profile PS1='[\u@ $PWD]> ' alias ls='ls -aF' export PATH="/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin" export TERM=vt100 21.30 在NetBSD 1.5.2上安装BASH A: 1) 安装wget pkg_add -v ftp://ftp.netbsd.org/pub/NetBSD/packages/1.5/i386/All/gettext-lib-0.10.35nb1.tgz pkg_add -v ftp://ftp.netbsd.org/pub/NetBSD/packages/1.5/i386/All/wget-1.7.tgz ls -l /usr/pkg/bin/wget ln -s /usr/pkg/bin/wget /bin/wget 2) 安装bash pkg_add -v ftp://ftp.netbsd.org/pub/NetBSD/packages/1.5/i386/All/bash-2.05.tgz ls -l /usr/pkg/bin/bash ln -s /usr/pkg/bin/bash /bin/bash vi /etc/profile PS1='[\u@ $PWD]> ' alias ls='ls -aF' export PATH="/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/pkg/bin" export TERM=vt100 21.31 找不到何处启动了snmpd Q: 我用cvsup在FreeBSD 4.5-RELEASE上安装了ports里的NET-SNMP,系统重启后用 netstat -na、sockstat和ps -aux发现已经自动启动了/usr/local/sbin/snmpd。 我不想让它自动启动,"find /etc -type f | xargs grep -i snmp"却没有找到 想像中的启动脚本。 A: 在/etc/defaults/rc.conf文件中有如下内容 # startup script dirs. local_startup="/usr/local/etc/rc.d /usr/X11R6/etc/rc.d" 执行 /usr/local/etc/rc.d/snmpd.sh < start | stop > 如果想彻底禁止系统启动时自动启动snmpd,可以删除snmpd.sh 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 (Luther 整理) 通过/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 忽略 3) /proc/sys/net/ipv4/tcp_fin_timeout TIME_WAIT状态保持时间,缺省60s # echo 5 > /proc/sys/net/ipv4/tcp_fin_timeout 23. Linux相关问题 23.0 以POST方式提交URL请求 A: san $ echo "uid=username&password=secret" | lynx -post_data http://www.url.com A: tombkeeper curl -v http://cygnus.tele.pw.edu.pl/cgi-bin/environ.pl -d xxx 实际上原始请求报文只要包含如下内容即可: POST /cgi-bin/environ.pl HTTP/1.0\r\n Content-Length: 3\r\n \r\n xxx 某些WWW服务软件可以这样使用: GET /cgi-bin/environ.pl HTTP/1.0\r\n Content-Length: 3\r\n \r\n xxx Sambar Server允许,IIS不允许。 POST提交参数时必须指定"Content-Type: application/x-www-form-urlencoded\n": -------------------------------------------------------------------------- POST //victim.php HTTP/1.1\n Host: \n Content-Length: 36\n Content-Type: application/x-www-form-urlencoded\n Connection: Close\n \n conf=/tmp/xxxxxxxxxxxxxxxxxxxxxxxxxx -------------------------------------------------------------------------- 自己计算Content-Length。上述HTTP请求类似这个效果: http:////victim.php?conf=/tmp/xxxxxxxxxxxxxxxxxxxxxxxxxx 当然,GET方法被替换成POST方法了。 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 912/931/1413认证查询(113/TCP) wu-ftpd支持RFC 912/931/1413认证查询,大约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@APUE 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用不着这样。 D: baobaoer@www.linuxforum.net 2003-10-09 14:48 对于RedHat、SuSE,可以"cat /proc/version"。 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 这是缺省设置。如果未使能该选项,就会得到上述错误信息。 23.8 unknown terminal "vt100" Q: 远程ssh登录Linux服务器时提示: unknown terminal "vt100" 执行clear命令时失败,提示信息类似: 'vt100': unknown terminal type. A: scz 2005-07-21 13:56 以前没有这种问题,TERM环境变量一直设置成vt100: $ echo $TERM vt100 "su -"切换到root用户,执行clear命令后正常清屏。此时怀疑终端设置相关的某文 件权限被调整过,只有root用户才能访问,导致普通用户无法正常设置终端属性。 # strace -e trace=open clear 2>&1 | grep -i term open("/usr/share/terminfo/v/vt100", O_RDONLY) = 3 检查/usr/share/terminfo/v/vt100的权限,没有问题,层层回溯,直至/usr/share, 发现/usr/share的属主、属组、权限均被改动过,绝大多数普通用户没有该目录的rx 权限。正常情况下应该是: drwxr-xr-x root root /usr/share 修改成上述设置后,一切恢复正常。 24. Unix编程相关问题 24.0 如何知道fd是有效文件句柄 Q: 如果一个函数需要一个有效文件句柄(可以是pipe、fd、socket等等)做为形参, 有无办法判断其有效性。 A: Joe Halpin 我觉得最好是用fstat()函数进行判断,当fd不是一个有效文件句柄时,该函数返 回-1,并设置errno为EBADF。 A: Andrew Gierth fcntl( fd, F_GETFD, 0 ); 可能这是最快的办法,也最具有可移植性。在那些支持procfs的系统中,也可以 检查/proc//fd/目录下的伪文件,比如SPARC/Solaris 8和x86/Linux Kernel 2.4.7-10。而x86/FreeBSD 4.5-RELEASE中没有/proc//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 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 无论是哪种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 #include 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 #include #include #include #include #include #include #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 Flg 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 Flg Align LOAD 0x000000 0x08048000 0x08048000 0x00533 0x00533 RWE 0x1000 $ 用"objdump -x src | more"也能看到这些信息。 "law@APUE"曾经建议过这样的命令: $ ./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 chmod g-x 这是形象的表示,实际编程中应该通过chmod()函数一步完成。不能对目录、可执 行文件设置强制性锁。 24.3 如何编写daemon程序 Q: 在FreeBSD下用"ps auxw"查看进程列表时,注意到某些进程没有控制终端,也就 是说TT列显示??。知道这是所谓的daemon进程,如果我想自己编写这样的程序, 该如何做。 A: Andrew Gierth 这个回答来自著名的<>,由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 以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。参看<>。第七步 骤严格意义上来说,不是可选的,而是必须的。 参看<<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 以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@APUE 2003-07-11 FreeBSD和Linux直接提供了一个函数,DAEMON(3) -------------------------------------------------------------------------- DAEMON(3) FreeBSD库函数手册 DAEMON(3) 名字 daemon - 在后台运行程序 库 标准C库(libc, -lc) 语法 #include 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 #include int main ( int argc, char * argv[] ) { return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- A: scz $ 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 24.5 进程如何分辨谁在kill()自己 A: scz 2003-10-11 19:31 至少对于Linux、FreeBSD、Solaris、AIX这四种操作系统,有一种办法。不要安装传 统sa_handler信号句柄,而是安装sa_sigaction信号句柄。细节请man sigaction并 参照头文件加强理解。下面是一个可移植演示程序。 -------------------------------------------------------------------------- /* * For x86/Linux RedHat_8 2.4.18-14 * For x86/FreeBSD 4.5-RELEASE * For SPARC/Solaris 8 * For AIX 4.3.3.0 * * gcc -Wall -pipe -O3 -s -o siginfo_test siginfo_test.c */ /************************************************************************ * * * Head File * * * ************************************************************************/ #include #include #include #include #include #include #include #include /************************************************************************ * * * Macro * * * ************************************************************************/ /* * for signal handlers */ typedef void Sigfunc ( int, siginfo_t *, void * ); #define PRIVATE_SIG_ERR ((Sigfunc *)-1) /************************************************************************ * * * Function Prototype * * * ************************************************************************/ static void Atexit ( void ( * func ) ( void ) ); static void init_signal ( void ); static void init_timer ( unsigned int s ); static void on_alarm ( int signo, siginfo_t *si, void *unused ); static void on_segvbus ( int signo, siginfo_t *si, void *unused ); static void on_terminate ( int signo, siginfo_t *si, void *unused ); static Sigfunc * PrivateSignal ( int signo, Sigfunc *func ); static int Setitimer ( int which, struct itimerval *value, struct itimerval *ovalue ); static Sigfunc * Signal ( int signo, Sigfunc *func ); static void terminate ( void ); /************************************************************************ * * * Static Global Var * * * ************************************************************************/ static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0; /************************************************************************/ static void Atexit ( void ( * func ) ( void ) ) { if ( atexit( func ) != 0 ) { exit( EXIT_FAILURE ); } return; } /* end of Atexit */ /* * 初始化信号句柄 */ static void init_signal ( void ) { unsigned int i; Atexit( terminate ); for ( i = 1; i < 9; i++ ) { Signal( i, on_terminate ); } Signal( SIGTERM, on_terminate ); Signal( SIGALRM, on_alarm ); Signal( SIGSEGV, on_segvbus ); Signal( SIGBUS , on_segvbus ); return; } /* end of init_signal */ /* * 我们的定时器精度只支持到秒 */ static void init_timer ( unsigned int s ) { struct itimerval value; value.it_value.tv_sec = s; value.it_value.tv_usec = 0; /* * 只生效一次 */ value.it_interval.tv_sec = 0; value.it_interval.tv_usec = 0; Setitimer( ITIMER_REAL, &value, NULL ); return; } /* end of init_timer */ static void on_alarm ( int signo, siginfo_t *si, void *unused ) { fprintf ( stderr, "\n" "signo = %d\n" "si = 0x%08X\n" "unused = 0x%08X\n", signo, ( unsigned int )si, ( unsigned int )unused ); if ( NULL != si ) { fprintf ( stderr, "si->si_signo = %d\n" "si->si_errno = %d\n" "si->si_code = %d\n" "si->si_pid = %u\n" "si->si_uid = %u\n" "si->si_status = %d\n" "si->si_addr = 0x%08X\n", si->si_signo, si->si_errno, si->si_code, ( unsigned int )si->si_pid, ( unsigned int )si->si_uid, ( int )si->si_status, ( unsigned int )si->si_addr ); } return; } /* end of on_alarm */ static void on_segvbus ( int signo, siginfo_t *si, void *unused ) { fprintf ( stderr, "\n" "signo = %d\n" "si = 0x%08X\n" "unused = 0x%08X\n", signo, ( unsigned int )si, ( unsigned int )unused ); if ( NULL != si ) { fprintf ( stderr, "si->si_signo = %d\n" "si->si_errno = %d\n" "si->si_code = %d\n" "si->si_pid = %u\n" "si->si_uid = %u\n" "si->si_status = %d\n" "si->si_addr = 0x%08X\n", si->si_signo, si->si_errno, si->si_code, ( unsigned int )si->si_pid, ( unsigned int )si->si_uid, ( int )si->si_status, ( unsigned int )si->si_addr ); } if ( 0 == canjump ) { /* * unexpected signal, ignore */ return; } canjump = 0; /* * jump back to main, don't return */ siglongjmp( jmpbuf, signo ); } /* end of on_segvbus */ /* * 参如下头文件了解siginfo_t定义 * * Linux /usr/include/bits/siginfo.h * FreeBSD /usr/include/sys/signal.h * Solaris /usr/include/sys/siginfo.h * AIX /usr/include/sys/signal.h * * 第三形参不推荐使用,context相关。 */ static void on_terminate ( int signo, siginfo_t *si, void *unused ) { /* * 就上四种OS而言,测试Solaris,执行siginfo_test,按Ctrl-C进入该流程时 * si为NULL,其他三种系统则不同。 */ if ( NULL != si ) { /* * 演示用,不推荐在信号句柄中使用fprintf() */ fprintf ( stderr, "\n" "signo = %d\n" "si = 0x%08X\n" "unused = 0x%08X\n" "si->si_signo = %d\n" "si->si_errno = %d\n" "si->si_code = %d\n", signo, ( unsigned int )si, ( unsigned int )unused, si->si_signo, si->si_errno, si->si_code ); /* * si_code为SI_USER时意味着"signal sent by another process with kill()" * * 就上四种OS而言,我所测试的FreeBSD反应与其他三种不同,kill进程时 * si_code始终为0,而FreeBSD有如下定义: * * #define SI_USER 0x10001 * * 如果不判断si_code,强行显示si_pid、si_uid,对于FreeBSD而言总是0。 * 下面出于方便演示目的,没有判断si_code。正确作法应该判断si_code, * 然后显示联合的不同成员。 */ fprintf ( stderr, "si->si_pid = %u\n" "si->si_uid = %u\n" "si->si_status = %d\n" "si->si_addr = 0x%08X\n", ( unsigned int )si->si_pid, ( unsigned int )si->si_uid, ( int )si->si_status, ( unsigned int )si->si_addr ); } else { fprintf ( stderr, "\n" "signo = %d\n" "si = 0x%08X\n" "unused = 0x%08X\n", signo, ( unsigned int )si, ( unsigned int )unused ); } /* * 这次我们使用atexit()函数 */ exit( EXIT_SUCCESS ); } /* end of on_terminate */ /* * 与以前版本不同之处在于使用sa_sigaction,而不是sa_handler。 */ static Sigfunc * PrivateSignal ( int signo, Sigfunc *func ) { #if 0 from Linux /usr/include/bits/sigaction.h struct sigaction { union { /* * Used if SA_SIGINFO is not set. */ __sighandler_t sa_handler; /* * Used if SA_SIGINFO is set. */ void ( *sa_sigaction ) ( int, siginfo_t *, void * ); } __sigaction_handler; #define sa_handler __sigaction_handler.sa_handler #define sa_sigaction __sigaction_handler.sa_sigaction /* * Additional set of signals to be blocked. */ __sigset_t sa_mask; /* * Special flags. */ int sa_flags; /* * The sa_restorer element is obsolete and should not be used. * POSIX does not specify a sa_restorer element. * * Restore handler. */ void ( *sa_restorer ) ( void ); }; #endif struct sigaction act, oact; memset( &act, 0, sizeof( act ) ); sigemptyset( &act.sa_mask ); /* * Invoke signal-catching function with three arguments instead of one. */ act.sa_flags = SA_SIGINFO; act.sa_sigaction = func; if ( SIGALRM == signo ) { #ifdef SA_INTERRUPT /* * SunOS 4.x */ act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART /* * SVR4, 4.4BSD */ act.sa_flags |= SA_RESTART; #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( PRIVATE_SIG_ERR ); } return( oact.sa_sigaction ); } /* end of PrivateSignal */ static int Setitimer ( int which, 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 */ static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc; if ( PRIVATE_SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) ) { perror( "signal error" ); exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */ static void terminate ( void ) { /* * _exit( EXIT_SUCCESS ); */ return; } /* end of terminate */ int main ( int argc, char * argv[] ) { /* * for autovar, must be volatile */ volatile unsigned char *p; init_signal(); p = ( unsigned char * )&p; if ( 0 != sigsetjmp( jmpbuf, 1 ) ) { printf ( "p = 0x%08X\n", ( unsigned int )p ); goto main_continue; } /* * now sigsetjump() is OK */ canjump = 1; while ( 1 ) { /* * 诱发SIGSEGV、SIGBUS */ *p = *p; p++; } main_continue: /* * 启动定时器 */ init_timer( 1 ); while ( 1 ) { /* * 形成阻塞,降低CPU占用率 */ getchar(); } return( EXIT_SUCCESS ); } /* end of main */ /************************************************************************/ -------------------------------------------------------------------------- 这种技术是操作系统实现相关的。FreeBSD的si_code与头文件不相符。除了FreeBSD, 其他三种OS的si_addr如愿反映了栈底地址、si_pid/si_uid也能正确反映kill()信号 源。而Solaris会出现si为NULL的情形。下面是Linux上执行示例: [scz@ /home/scz/src]> ./siginfo_test signo = 11 si = 0xBFFFF6B0 unused = 0xBFFFF730 si->si_signo = 11 si->si_errno = 0 si->si_code = 1 si->si_pid = 3221225472 si->si_uid = 1869479936 si->si_status = 2712942 si->si_addr = 0xC0000000 <= 栈底地址 p = 0xC0000000 signo = 14 si = 0xBFFFF5A8 unused = 0xBFFFF628 si->si_signo = 14 si->si_errno = 0 si->si_code = 128 <= SI_KERNEL 0x80 Send by kernel. si->si_pid = 0 si->si_uid = 0 si->si_status = 896820224 si->si_addr = 0x00000000 ^Z [scz@ /home/scz/src]> bg %1 [scz@ /home/scz/src]> kill %1 signo = 15 si = 0xBFFFF5A8 unused = 0xBFFFF628 si->si_signo = 15 si->si_errno = 0 si->si_code = 0 <= SI_USER 0x00 Sent by kill, sigsend, raise. si->si_pid = 27712 <= kill()信号源 si->si_uid = 1000 <= kill()信号源 si->si_status = 896820224 si->si_addr = 0x00006C40 [scz@ /home/scz/src]> echo $$ 27712 [scz@ /home/scz/src]> id uid=1000(scz) gid=0(root) groups=0(root) [scz@ /home/scz/src]> 最后结论,对于x86/FreeBSD 4.5-RELEASE,无法利用该技术分辨kill()信号源。其 他三种操作系统可以利用该技术。 一个有趣的想法,进程分辨出kill()信号源,反向kill信号源。 D: law@APUE 2003-10-13 10:03 修改一下gstack.c,考虑栈向内存高址方向增长的情形。用递归方式确定堆栈增长方 向比较可靠,否则由于不同函数的不同优化可能导致误判,测试中碰上这种情形了。 -------------------------------------------------------------------------- /* * For x86/Linux RedHat_8 2.4.18-14 * For x86/FreeBSD 4.5-RELEASE * For SPARC/Solaris 8 * For AIX 4.3.3.0 * * gcc -Wall -pipe -O3 -s -o gstack gstack.c */ /************************************************************************ * * * Head File * * * ************************************************************************/ #include #include #include #include #include #include #include /************************************************************************ * * * Macro * * * ************************************************************************/ /* * for signal handlers */ typedef void Sigfunc ( int, siginfo_t *, void * ); #define PRIVATE_SIG_ERR ((Sigfunc *)-1) /* * 向上指向高址方向增长,向下指向低址方向增长,后者最常见 */ #define STACKUP 0 #define STACKDOWN 1 /************************************************************************ * * * Function Prototype * * * ************************************************************************/ static unsigned char * get_stack_bottom ( void ); static void on_segvbus ( int signo, siginfo_t *si, void *unused ); static Sigfunc * PrivateSignal ( int signo, Sigfunc *func ); static Sigfunc * Signal ( int signo, Sigfunc *func ); static unsigned int stack_grow ( unsigned int level, unsigned int *addr ); /************************************************************************ * * * Static Global Var * * * ************************************************************************/ /* * start of .text */ extern int _etext; /* * start of .data */ extern int _edata; /* * start of heap */ extern int _end; static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0; static Sigfunc *orig_segv = PRIVATE_SIG_ERR; static Sigfunc *orig_bus = PRIVATE_SIG_ERR; /************************************************************************/ static unsigned char * get_stack_bottom ( void ) { /* * for autovar, must be volatile */ volatile unsigned char *p = NULL; orig_segv = Signal( SIGSEGV, on_segvbus ); orig_bus = Signal( SIGBUS , on_segvbus ); p = ( unsigned char * )&p; if ( 0 != sigsetjmp( jmpbuf, 1 ) ) { Signal( SIGSEGV, orig_segv ); Signal( SIGBUS , orig_bus ); goto get_stack_bottom_exit; } /* * now sigsetjump() is OK */ canjump = 1; if ( STACKUP == stack_grow( 0, NULL ) ) { while ( 1 ) { /* * 诱发SIGSEGV、SIGBUS */ *p = *p; p--; } } else { while ( 1 ) { /* * 诱发SIGSEGV、SIGBUS */ *p = *p; p++; } } get_stack_bottom_exit: return( ( unsigned char * )p ); } /* end of get_stack_bottom */ static void on_segvbus ( int signo, siginfo_t *si, void *unused ) { fprintf ( stderr, "signo = %d\n" "si = 0x%08X\n" "unused = 0x%08X\n", signo, ( unsigned int )si, ( unsigned int )unused ); if ( NULL != si ) { fprintf ( stderr, "si->si_signo = %d\n" "si->si_errno = %d\n" "si->si_code = %d\n" "si->si_addr = 0x%08X\n", si->si_signo, si->si_errno, si->si_code, ( unsigned int )si->si_addr ); } if ( 0 == canjump ) { /* * unexpected signal, ignore */ return; } canjump = 0; /* * jump back to get_stack_bottom, don't return */ siglongjmp( jmpbuf, signo ); } /* end of on_segvbus */ /* * 与以前版本不同之处在于使用sa_sigaction,而不是sa_handler。 */ static Sigfunc * PrivateSignal ( int signo, Sigfunc *func ) { #if 0 from Linux /usr/include/bits/sigaction.h struct sigaction { union { /* * Used if SA_SIGINFO is not set. */ __sighandler_t sa_handler; /* * Used if SA_SIGINFO is set. */ void ( *sa_sigaction ) ( int, siginfo_t *, void * ); } __sigaction_handler; #define sa_handler __sigaction_handler.sa_handler #define sa_sigaction __sigaction_handler.sa_sigaction /* * Additional set of signals to be blocked. */ __sigset_t sa_mask; /* * Special flags. */ int sa_flags; /* * The sa_restorer element is obsolete and should not be used. * POSIX does not specify a sa_restorer element. * * Restore handler. */ void ( *sa_restorer ) ( void ); }; #endif struct sigaction act, oact; memset( &act, 0, sizeof( act ) ); sigemptyset( &act.sa_mask ); /* * Invoke signal-catching function with three arguments instead of one. */ act.sa_flags = SA_SIGINFO; act.sa_sigaction = func; if ( SIGALRM == signo ) { #ifdef SA_INTERRUPT /* * SunOS 4.x */ act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART /* * SVR4, 4.4BSD */ act.sa_flags |= SA_RESTART; #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( PRIVATE_SIG_ERR ); } return( oact.sa_sigaction ); } /* end of PrivateSignal */ static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc; if ( PRIVATE_SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) ) { perror( "signal error" ); exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */ static unsigned int stack_grow ( unsigned int level, unsigned int *addr ) { unsigned int dummy; unsigned int ret; if ( 0 == level ) { ret = stack_grow( level + 1, &dummy ); } else { if ( ( unsigned int )addr > ( unsigned int )&dummy ) { ret = STACKDOWN; } else { ret = STACKUP; } printf ( "stack_level_0 = 0x%08X\n" "stack_level_1 = 0x%08X\n" "stack grow = %s/%u\n", ( unsigned int )addr, ( unsigned int )&dummy, ( STACKUP == ret ) ? "UP/HIGH" : "DOWN/LOW", ret ); } return( ret ); } /* end of stack_grow */ int main ( int argc, char * argv[] ) { unsigned char *p; p = get_stack_bottom(); printf ( "_etext = 0x%08X\n" "_edata = 0x%08X\n" "_end = 0x%08X\n" "stack bottom = 0x%08X\n" "&p = 0x%08X\n", ( unsigned int )&_etext, ( unsigned int )&_edata, ( unsigned int )&_end, ( unsigned int )p, ( unsigned int )&p ); return( EXIT_SUCCESS ); } /* end of main */ /************************************************************************/ -------------------------------------------------------------------------- 这是在AIX 4.3.3.0上的执行效果: > ./gstack stack_level_0 = 0x2FF22B40 stack_level_1 = 0x2FF22AF8 stack grow = DOWN/LOW/1 <= 栈向低址方向增长 signo = 11 si = 0x2FF22A10 unused = 0x2FF22780 si->si_signo = 11 si->si_errno = 0 si->si_code = 51 si->si_addr = 0x2FF23000 <= 栈底地址 _etext = 0x10000E40 _edata = 0x20000ED4 _end = 0x200010C0 stack bottom = 0x2FF23000 <= 栈底地址 &p = 0x2FF22BC8 24.6 getopt()、getopt_long()、getopt_long_only()是何用法 Q: 能给一个getopt()的例子吗 A: 小四 1999-04-05 17:02 -------------------------------------------------------------------------- /* * For x86/Linux * gcc -Wall -pipe -O3 -s -o getopt_test getopt_test.c */ #include #include #include #include #include #define VERSION "1.00 1999-04-05 17:02" static void usage ( char *arg ) { fprintf ( stderr, "Usage: %s [-h] [-v] [-i intvar] [-s strvar]\n", arg ); exit( EXIT_FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; int c; unsigned int intvar = 0; char *strvar = NULL; if ( 1 == argc ) { usage( argv[0] ); } /* * don't want getopt() writing to stderr */ opterr = 0; /* * getopt()第三形参最前面如果有冒号,就不会出现illegal option错误信息。 * 不过,如果指定了"opterr = 0",有无这个前导冒号都无所谓了。 */ while ( EOF != ( c = getopt( argc, argv, ":hi:s:v" ) ) ) { switch ( c ) { case 'i': intvar = ( unsigned int )strtoul( optarg, NULL, 0 ); break; case 's': strvar = optarg; break; case 'v': printf( "%s ver "VERSION"\n", argv[0] ); return( EXIT_SUCCESS ); case 'h': case '?': default : usage( argv[0] ); break; } /* end of switch */ } /* end of while */ argc -= optind; argv += optind; printf ( "intvar = %u\n" "strvar = %s\n", intvar, NULL == strvar ? "(null)" : strvar ); ret = EXIT_SUCCESS; return( ret ); } /* end of main */ #if 0 $ ./getopt_test -h Usage: ./getopt_test [-h] [-v] [-i intvar] [-s strvar] $ ./getopt_test -i 1314 -s scz intvar = 1314 strvar = scz $ ./getopt_test -v ./getopt_test ver 1.00 1999-04-05 17:02 #endif -------------------------------------------------------------------------- Q: 现在会用getopt()了,但如何支持"--parami intvar --params strvar"呢 A: 小四 2001-12-17 15:30 这需要用到getopt_long(),但这不是一个具有良好可移植性的函数。 -------------------------------------------------------------------------- /* * For x86/Linux * gcc -Wall -pipe -O3 -s -o getopt_long_test getopt_long_test.c */ #include #include #include #include #include /* * 为了使用getopt_long(),必须有如下两行内容 */ #define _GNU_SOURCE #include #define VERSION "1.00 2001-12-17 15:30" /* * 不要与短选项所用的字符相同! */ #define LONGOPTIONCHAR '*' static void usage ( char *arg ) { fprintf ( stderr, "Usage: %s [-h] [--ver] [--parami intvar] [-params strvar]\n", arg ); exit( EXIT_FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; int c; unsigned int intvar = 0; char *strvar = NULL; struct option longOption[] = { { "ver", 0, NULL, LONGOPTIONCHAR }, { "parami", 1, NULL, LONGOPTIONCHAR }, { "params", 1, NULL, LONGOPTIONCHAR }, { NULL, 0, NULL, 0 } }; int longOptionIndex = 0; if ( 1 == argc ) { usage( argv[0] ); } /* * don't want getopt() writing to stderr */ opterr = 0; while ( EOF != ( c = getopt_long( argc, argv, ":h", longOption, &longOptionIndex ) ) ) { switch ( c ) { case LONGOPTIONCHAR: if ( optarg ) { switch ( longOptionIndex ) { case 1: intvar = ( unsigned int )strtoul( optarg, NULL, 0 ); break; case 2: strvar = optarg; break; default: break; } /* end of switch */ } else { switch ( longOptionIndex ) { /* * longOption[]的下标 */ case 0: printf( "%s ver "VERSION"\n", argv[0] ); return( EXIT_SUCCESS ); default: break; } /* end of switch */ } break; case 'h': case '?': default : usage( argv[0] ); break; } /* end of switch */ } /* end of while */ argc -= optind; argv += optind; printf ( "intvar = %u\n" "strvar = %s\n", intvar, NULL == strvar ? "(null)" : strvar ); ret = EXIT_SUCCESS; return( ret ); } /* end of main */ #if 0 $ ./getopt_long_test -h Usage: ./getopt_long_test [-h] [--ver] [--parami intvar] [-params strvar] $ ./getopt_long_test --parami 1314 --params scz intvar = 1314 strvar = scz $ ./getopt_long_test --ver ./getopt_long_test ver 1.00 2001-12-17 15:30 #endif -------------------------------------------------------------------------- Q: 那getopt_long_only()与getopt_long()的区别是什么 A: 小四 2005-05-09 10:48 getopt_long_only()的行为与getopt_long()非常类似,但不限于用'--'引入长选项, '-'亦可引入长选项。当'-'引入的选项不匹配任何长选项时,继续进行短选项匹配。 再次提醒,getopt_long_only()与getopt_long()一样,不具有良好可移植性。 -------------------------------------------------------------------------- /* * For x86/Linux * gcc -Wall -pipe -O3 -s -o getopt_long_only_test getopt_long_only_test.c */ #include #include #include #include #include /* * 为了使用getopt_long_only(),必须有如下两行内容 */ #define _GNU_SOURCE #include #define VERSION "1.00 2005-05-09 10:48" /* * 不要与短选项所用的字符相同!这次用空格符,更可靠些。 */ #define LONGOPTIONCHAR ' ' static void usage ( char *arg ) { fprintf ( stderr, "Usage: %s [-h] [--v] [-i intvar] [--s strvar]\n", arg ); exit( EXIT_FAILURE ); } /* end of usage */ int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; int c; unsigned int intvar = 0; char *strvar = NULL; struct option longOption[] = { { "v", 0, NULL, LONGOPTIONCHAR }, { "s", 1, NULL, LONGOPTIONCHAR }, { NULL, 0, NULL, 0 } }; int longOptionIndex = 0; if ( 1 == argc ) { usage( argv[0] ); } /* * don't want getopt() writing to stderr */ opterr = 0; while ( EOF != ( c = getopt_long_only( argc, argv, ":hi:", longOption, &longOptionIndex ) ) ) { switch ( c ) { case LONGOPTIONCHAR: if ( optarg ) { switch ( longOptionIndex ) { case 1: strvar = optarg; break; default: break; } /* end of switch */ } else { switch ( longOptionIndex ) { /* * longOption[]的下标 */ case 0: printf( "%s ver "VERSION"\n", argv[0] ); return( EXIT_SUCCESS ); default: break; } /* end of switch */ } break; case 'i': intvar = ( unsigned int )strtoul( optarg, NULL, 0 ); break; case 'h': case '?': default : usage( argv[0] ); break; } /* end of switch */ } /* end of while */ argc -= optind; argv += optind; printf ( "intvar = %u\n" "strvar = %s\n", intvar, NULL == strvar ? "(null)" : strvar ); ret = EXIT_SUCCESS; return( ret ); } /* end of main */ #if 0 $ ./getopt_long_only_test -h Usage: ./getopt_long_only_test [-h] [--v] [-i intvar] [--s strvar] $ ./getopt_long_only_test --v ./getopt_long_only_test ver 1.00 2005-05-09 10:48 $ ./getopt_long_only_test -v ./getopt_long_only_test ver 1.00 2005-05-09 10:48 $ ./getopt_long_only_test -i 1314 -s scz intvar = 1314 strvar = scz #endif -------------------------------------------------------------------------- 24.7 能否在connect()之前得到本地端口号 A: scz 有人妄图创建套接字之后直接调用getsockname()获得本地端口号,但理论与实践皆 否定了这个妄想。要么先调用bind()指定本地端口号,要么等到connect()之后调用 getsockname()。 -------------------------------------------------------------------------- /* * For x86/Linux Kernel 2.4.18-14 (don't use -static sometimes because SIGSEGV, try it anyway) * gcc -DLinux -Wall -pipe -O3 -s -o getsockname_test getsockname_test.c * * For x86/FreeBSD 4.5-RELEASE (maybe you should try -static) * gcc -DFreeBSD -Wall -pipe -O3 -s -o getsockname_test getsockname_test.c * * For SPARC/Solaris 8 * gcc -DSparc -Wall -pipe -O3 -s -o getsockname_test getsockname_test.c -lsocket -lnsl * * For x86/Solaris 8 * gcc -DSolaris -Wall -pipe -O3 -s -o getsockname_test getsockname_test.c -lsocket -lnsl * * For AIX 4.3.3.0 * gcc -DAix -Wall -pipe -O3 -s -o getsockname_test getsockname_test.c * * (strip getsockname_test or gcc -s) */ #include #include #include #include #include #include #include #include #ifdef FreeBSD #include #include #endif #include #include #include int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; struct sockaddr_in sin; socklen_t sinlen; int s = -1; if ( ( s = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 ) { perror( "socket error" ); goto main_exit; } memset( &sin, 0, sizeof( sinlen ) ); sinlen = sizeof( sin ); if ( getsockname( s, ( struct sockaddr * )&sin, &sinlen ) < 0 ) { perror( "getsockname error" ); goto main_exit; } printf ( "port = %d\n", ntohs( sin.sin_port ) ); if ( -1 != s ) { close( s ); s = -1; } if ( ( s = socket( PF_INET, SOCK_DGRAM, 0 ) ) < 0 ) { perror( "socket error" ); goto main_exit; } memset( &sin, 0, sizeof( sinlen ) ); sinlen = sizeof( sin ); if ( getsockname( s, ( struct sockaddr * )&sin, &sinlen ) < 0 ) { perror( "getsockname error" ); goto main_exit; } printf ( "port = %d\n", ntohs( sin.sin_port ) ); ret = EXIT_SUCCESS; main_exit: if ( -1 != s ) { close( s ); s = -1; } return( ret ); } /* end of main */ -------------------------------------------------------------------------- 24.8 如何在GB2312与Unicode之间互相转换 Q: 在Windows上有WideCharToMultiByte()、MultiByteToWideChar()等函数可用,Unix 上有类似函数吗。 A: scz 请"man iconv_open",然后动用Google进行相关搜索。之所以没有建议"man iconv", 因为iconv(1)对应着一个命令,Linux上应该看iconv(3),Solaris上应该看iconv(3C), 其它系统各有变化,但"man iconv_open"总是一致的。 我们就iconv*()函数进行了简单测试: -------------------------------------------------------------------------- /* * gcc -DLinux -Wall -pipe -O3 -s -o iconv_test iconv_test.c * gcc -DSparc -Wall -pipe -O3 -s -o iconv_test iconv_test.c */ #if 0 我的一台FreeBSD测试机上居然没有/usr/include/iconv.h 而另一台AIX测试机上则根本不支持GB2312,编译时需指定库 gcc -DAix -Wall -pipe -O3 -s -o iconv_test iconv_test.c -liconv 显然,在各个缺省安装的Unix系统上iconv*()被支持的程度不一,所非自行安装 libiconv,否则这是一个高度不可移植的选择,决定放弃iconv*()。 本程序仅为测试目的存在,没有实用价值。 #endif #include #include #include #include #include static void outputBinary ( FILE *out, unsigned char *byteArray, size_t byteArrayLen ) { size_t offset, k, j, i; fprintf( out, "byteArray [ %u bytes ] ->\n", ( unsigned int )byteArrayLen ); if ( byteArrayLen <= 0 ) { return; } i = 0; offset = 0; for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 ) { fprintf( out, "%08X ", ( unsigned int )offset ); for ( j = 0; j < 16; j++, i++ ) { if ( j == 8 ) { fprintf( out, "-%02X", byteArray[i] ); } else { fprintf( out, " %02X", byteArray[i] ); } } fprintf( out, " " ); i -= 16; for ( j = 0; j < 16; j++, i++ ) { /* * if ( isprint( (int)byteArray[i] ) ) */ #if 1 if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] != 0x7F ) && ( byteArray[i] < 0xFF ) ) #else if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] < 0x7F ) ) #endif { fprintf( out, "%c", byteArray[i] ); } else { fprintf( out, "." ); } } fprintf( out, "\n" ); } /* end of for */ k = byteArrayLen - i; if ( k <= 0 ) { return; } fprintf( out, "%08X ", ( unsigned int )offset ); for ( j = 0 ; j < k; j++, i++ ) { if ( j == 8 ) { fprintf( out, "-%02X", byteArray[i] ); } else { fprintf( out, " %02X", byteArray[i] ); } } i -= k; for ( j = 16 - k; j > 0; j-- ) { fprintf( out, " " ); } fprintf( out, " " ); for ( j = 0; j < k; j++, i++ ) { #if 1 if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] != 0x7F ) && ( byteArray[i] < 0xFF ) ) #else if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] < 0x7F ) ) #endif { fprintf( out, "%c", byteArray[i] ); } else { fprintf( out, "." ); } } fprintf( out, "\n" ); return; } /* end of outputBinary */ #ifdef Linux int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; iconv_t cd = ( iconv_t )-1; char in[] = "GB2312与Unicode双向转换测试"; size_t inlen = sizeof( in ); char *inp = in; char out[sizeof(in)*2]; size_t outlen = sizeof( out ); char *outp = out; /* * 大小写不敏感 */ if ( ( iconv_t )-1 == ( cd = iconv_open( "UCS-2", "GB2312" ) ) ) { perror( "iconv_open() error" ); goto main_exit; } outputBinary ( stderr, in, sizeof( in ) ); if ( -1 == iconv( cd, &inp, &inlen, &outp, &outlen ) ) { perror( "iconv() error" ); goto main_exit; } outputBinary ( stderr, out, sizeof( out ) - outlen ); outlen = sizeof( out ) - outlen; outp = out; inlen = sizeof( in ); inp = in; if ( ( iconv_t )-1 != cd ) { iconv_close( cd ); cd = ( iconv_t )-1; } if ( ( iconv_t )-1 == ( cd = iconv_open( "GB2312", "UCS-2" ) ) ) { perror( "iconv_open() error" ); goto main_exit; } if ( -1 == iconv( cd, &outp, &outlen, &inp, &inlen ) ) { perror( "iconv() error" ); goto main_exit; } outputBinary ( stderr, in, sizeof( in ) - inlen ); ret = EXIT_SUCCESS; main_exit: if ( ( iconv_t )-1 != cd ) { iconv_close( cd ); cd = ( iconv_t )-1; } return( ret ); } /* end of main */ #if 0 [scz@ /home/scz/src]> ./iconv_test byteArray [ 28 bytes ] -> 00000000 47 42 32 33 31 32 D3 EB-55 6E 69 63 6F 64 65 CB GB2312与Unicode? 00000010 AB CF F2 D7 AA BB BB B2-E2 CA D4 00 蜃徊馐? byteArray [ 42 bytes ] -> 00000000 47 00 42 00 32 00 33 00-31 00 32 00 0E 4E 55 00 G.B.2.3.1.2..NU. 00000010 6E 00 69 00 63 00 6F 00-64 00 65 00 CC 53 11 54 n.i.c.o.d.e.蘏.T 00000020 6C 8F 62 63 4B 6D D5 8B-00 00 l廱cKm諎.. byteArray [ 28 bytes ] -> 00000000 47 42 32 33 31 32 D3 EB-55 6E 69 63 6F 64 65 CB GB2312与Unicode? 00000010 AB CF F2 D7 AA BB BB B2-E2 CA D4 00 蜃徊馐? #endif #elif defined(Sparc) int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; iconv_t cd = ( iconv_t )-1; char in[] = "GB2312与Unicode双向转换测试"; size_t inlen = sizeof( in ); char *inp = in; char out[sizeof(in)*2]; size_t outlen = sizeof( out ); char *outp = out; char xxx[sizeof(out)]; size_t xxxlen = sizeof( xxx ); char *xxxp = xxx; /* * 大小写敏感,SPARC/Solaris上无法直接从"gb2312"转到"UCS-2" */ if ( ( iconv_t )-1 == ( cd = iconv_open( "UTF-8", "gb2312" ) ) ) { perror( "iconv_open() error" ); goto main_exit; } outputBinary ( stderr, in, sizeof( in ) ); if ( -1 == iconv( cd, ( const char ** )&inp, &inlen, &outp, &outlen ) ) { perror( "iconv() error" ); goto main_exit; } outputBinary ( stderr, out, sizeof( out ) - outlen ); outlen = sizeof( out ) - outlen; outp = out; if ( ( iconv_t )-1 != cd ) { iconv_close( cd ); cd = ( iconv_t )-1; } if ( ( iconv_t )-1 == ( cd = iconv_open( "UCS-2", "UTF-8" ) ) ) { perror( "iconv_open() error" ); goto main_exit; } if ( -1 == iconv( cd, ( const char ** )&outp, &outlen, &xxxp, &xxxlen ) ) { perror( "iconv() error" ); goto main_exit; } outputBinary ( stderr, xxx, sizeof( xxx ) - xxxlen ); xxxlen = sizeof( xxx ) - xxxlen; xxxp = xxx; outlen = sizeof( out ); outp = out; if ( ( iconv_t )-1 != cd ) { iconv_close( cd ); cd = ( iconv_t )-1; } if ( ( iconv_t )-1 == ( cd = iconv_open( "UTF-8", "UCS-2" ) ) ) { perror( "iconv_open() error" ); goto main_exit; } if ( -1 == iconv( cd, ( const char ** )&xxxp, &xxxlen, &outp, &outlen ) ) { perror( "iconv() error" ); goto main_exit; } outputBinary ( stderr, out, sizeof( out ) - outlen ); outlen = sizeof( out ) - outlen; outp = out; inlen = sizeof( in ); inp = in; if ( ( iconv_t )-1 != cd ) { iconv_close( cd ); cd = ( iconv_t )-1; } if ( ( iconv_t )-1 == ( cd = iconv_open( "gb2312", "UTF-8" ) ) ) { perror( "iconv_open() error" ); goto main_exit; } if ( -1 == iconv( cd, ( const char ** )&outp, &outlen, &inp, &inlen ) ) { perror( "iconv() error" ); goto main_exit; } outputBinary ( stderr, in, sizeof( in ) - inlen ); ret = EXIT_SUCCESS; main_exit: if ( ( iconv_t )-1 != cd ) { iconv_close( cd ); cd = ( iconv_t )-1; } return( ret ); } /* end of main */ #if 0 [scz@ /export/home/scz/src]> ./iconv_test byteArray [ 28 bytes ] -> 00000000 47 42 32 33 31 32 D3 EB-55 6E 69 63 6F 64 65 CB GB2312与Unicode? 00000010 AB CF F2 D7 AA BB BB B2-E2 CA D4 00 蜃徊馐? byteArray [ 35 bytes ] -> 00000000 47 42 32 33 31 32 E4 B8-8E 55 6E 69 63 6F 64 65 GB2312涓嶶nicode 00000010 E5 8F 8C E5 90 91 E8 BD-AC E6 8D A2 E6 B5 8B E8 鍙屽悜杞崲娴嬭 00000020 AF 95 00 瘯. byteArray [ 44 bytes ] -> 00000000 FE FF 00 47 00 42 00 32-00 33 00 31 00 32 4E 0E ?.G.B.2.3.1.2N. 00000010 00 55 00 6E 00 69 00 63-00 6F 00 64 00 65 53 CC .U.n.i.c.o.d.eS? 00000020 54 11 8F 6C 63 62 6D 4B-8B D5 00 00 T.弆cbmK嬚.. byteArray [ 35 bytes ] -> 00000000 47 42 32 33 31 32 E4 B8-8E 55 6E 69 63 6F 64 65 GB2312涓嶶nicode 00000010 E5 8F 8C E5 90 91 E8 BD-AC E6 8D A2 E6 B5 8B E8 鍙屽悜杞崲娴嬭 00000020 AF 95 00 瘯. byteArray [ 28 bytes ] -> 00000000 47 42 32 33 31 32 D3 EB-55 6E 69 63 6F 64 65 CB GB2312与Unicode? 00000010 AB CF F2 D7 AA BB BB B2-E2 CA D4 00 蜃徊馐? #endif #endif -------------------------------------------------------------------------- 鉴于缺省情况下iconv*()不具有良好的可移植性,可以考虑其它方案。其中一种替代 方案是在.c中预定义相关码表,自行完成查表转换工作。但这种方案要考虑字节序的 问题,参看前面x86/Linux、SPARC/Solaris上的输出信息,也不具有良好可移植性。 24.9 printf()时想显示文件名及行号 A: -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -s -o test test.c */ #include #include int main ( int argc, char * argv[] ) { printf( "%s:%u\n", __FILE__, __LINE__ ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- $ ./test test.c:9 __LINE__从1开始计。 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 于是我假设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被重新定义过: #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。 25.5 AIX如何进入单用户模式 A: maldini3@SMTH 2004-11-10 在shell中执行"shutdown -m",也可以在启动时按面板上的开关(MAC总线)或者F5/F6 功能键(PCI总线)。