-------------------------------------------------------------------------- 名称 -- Unix编程/应用问答中文版(2014-03-24 11:23外发版) 链接 -- https://scz.617.cn/unix/201403241123.txt 维护 -- 小四 主页 -- http://www.nsfocus.com 创建 -- 2001-02-05 13:49 更新 -- 2014-03-24 10:10 感谢 -- 感谢C语言的发明者、Unix操作系统的发明者,感谢全世界C程序员创造的Unix共 享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。 感谢deepin,在早期维护过程中的支持、帮助和鼓励。感谢所有NSFOCUS安全研 究小组(security@nsfocus.com)的朋友。 主要支持人员(字母顺序) -- Andrew Gierth backend Casper H.S. Dik deepin jbtzhm law qfp scz suxm tk 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交流。 -------------------------------------------------------------------------- 更新说明(2014-03-24 11:23) -- 很多问答已经只具有历史意义,不再有现实意义,我未删除这部分内容。 时隔10年,更新了一版,让某些爱好者(假设确实存在的话)久等了。 感谢小钻风(TeLeMan)提供个人主页空间,使得此次更新得以见到明天的太阳。 -------------------------------------------------------------------------- 目录 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 粘滞位的意义 1.16 Solaris上root设置普通用户口令时提示权限否定 1.17 Solaris禁止root帐号远程telnet 1.18 如何关机、重启 1.19 Solaris允许root帐号远程ftp 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调试时没有符号表,如何设置断点 2.9 在GDB里修改.text内容 2.10 GDB调试时碰上"Cannot insert breakpoint" 2.11 GDB中以Intel风格反汇编 2.12 在GDB中调用被调试进程空间中的函数 2.13 在GDB中x/4i时不想看到符号式偏移 2.14 在GDB中bt查看调用栈回溯时输出太过冗长 2.15 GDB断点后处理commands中finish/until/tb带来的问题 2.16 在GDB里以C语言数组形式转储数据 2.17 在GDB里将指定范围的内存数据转储成二进制文件 2.18 如何从BIND产生的core中获知BIND的版本信息 2.19 在GDB里如何搜索内存 2.20 在GDB里第N次经过断点时断下来 2.21 在GDB里进行源码级调试时的常用命令 2.22 在GDB里如何查看GS:0x10的内容 2.23 ELF Auxiliary Vectors 2.24 如何用GDB调试子进程 2.25 Debian strace(1) 2.26 Debian ltrace(1) 2.27 GDB中一些有用的info命令 2.28 GDB中如何清屏 2.29 在GDB中如何demangle C++符号 2.30 GDB中如何禁止"Type to continue, or q to quit" 2.31 GDB中如何调用指定动态链接库中的导出函数 2.32 GDB调试多线程进程时如何只调试指定线程同时挂起其他线程 2.33 Valgrind使用简介 2.34 用Mudflap检查内存错误 2.35 如何检查内存泄漏 2.36 用MALLOC_CHECK_检查内存错误 2.37 用mtrace检查内存错误 2.38 GDB中如何调试signal handler 2.39 手工编译安装新版本的GDB 2.40 源码级调试GLIBC 2.41 -g与-ggdb的区别 2.42 Debian latrace(1) 2.43 "b main"与"b *main"有什么区别 2.44 ~/.gdbinit 2.45 GDB中如何hexdump 2.46 加载、卸载.so时断下来 3. -lelf、-lkvm、-lkstat相关问题 3.0 3.1 如何判断可执行文件是否携带了调试信息 3.2 mprotect如何用 3.3 mmap如何用 3.4 3.5 setitimer如何用 3.6 execstack命令 3.7 paxtest命令 3.8 如何判断当前系统启用了ASLR 3.9 如何关闭ASLR 3.10 GCC编译链接时去除栈保护 3.11 Linux系统的DEP 3.12 exec*()对ASLR的影响 3.13 如何获取ELF文件的入口点(e_entry) 3.14 如何获取ELF文件加载基址 3.15 sigaltstack(2) 4. 系统资源相关问题 4.0 Linux线程栈的大小 4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况 4.2 Solaris上如何获知CPU速率 4.3 如何编程获取Solaris系统当前内存大小 4.4 Solaris上free()的内存如何还给OS 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 Solaris 10如何禁止自动挂接U盘、光盘、软盘 5.13 重启vold失败 5.14 给虚拟机里的/dev/sda1扩容 5.15 /etc/fstab与UUID 6. 可调资源限制 6.1 Solaris下如何限制每个用户可拥有的最大进程数 6.2 如何配置系统使之支持更多的伪终端 6.3 如何增加每个进程可打开文件句柄数 6.4 Linux如何产生core dump 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如何指定域名解析的顺序 7.6 什么时候会用到53/TCP 7.7 使用53/TCP进行域名解析 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 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 Solaris怎样将第二块网卡名改成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下网卡重命名 10.19 Solaris如何手工配置TCP/IP协议栈 10.20 Debian如何更改网卡名 10.21 Debian上如何阻止本机发送端口不可达ICMP报文、RST报文 10.22 Debian上如何禁用IPv6 10.23 如何禁用SSLv2 10.24 如何架设SSH Tunnel 10.25 利用privoxy将SOCKS5代理转换为HTTP代理 10.26 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下如何检查文件是否被改动过 11.8 如何用tar直接解.tar.bz2文件 11.9 Debian上如何知道指定文件属于哪个安装包 11.10 装完Debian发现少了fprintf的man手册 11.11 查看fprintf的man手册时遭遇"can't find macro file gb.tmac" 11.12 Solaris上如何安装package 11.13 Solaris上如何卸载package 11.14 如何单独获得Solaris编译环境 11.15 在Debian 4.0上安装acroread 11.16 使用apt系列工具时总是出现"Segment fault" 11.17 dpkg: 'update-rc.d' not found on PATH 11.18 "apt-get update -u"时遭遇"There is no public key available for the following key IDs" 11.19 如何安装Perl模块 11.20 "man pthread_cond_wait"说没有手册页 11.21 Dynamic MMap ran out of room. Please increase the size of APT::Cache-Start 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文件看上去太大了 12.5 如何对付.bash_history 13. 进程相关问题 13.0 显示进程树 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 13.15 fork()+exec*()的exec*()返回-1时如何通知父进程 13.16 wait3/wait4/getrusage/waitpid 14. 一些小工具的使用 14.0 paste命令 14.1 如何在命令行上进行8进制、10进制、16进制之间的转换 14.2 显示文件的三个时间戳(atime、mtime、ctime) 14.3 只在本地文件系统上查找 14.4 join命令 14.5 反汇编 14.6 wget指定代理 14.7 curl使用技巧 14.8 Debian上有什么类似Windows上fc.exe的工具 14.9 iconv命令 14.10 如何计算MD5、SHA-1 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 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文件时如何指定引出哪些符号 16.16 编译源码时configure相关问题 16.17 编译GDB时遭遇"configure: error: no termcap library found" 16.18 用gcc-2.95编译程序时遭遇"/usr/bin/ld: crt1.o: No such file: No such file or directory" 16.19 在链接时遭遇"undefined reference to tputs" 16.20 Linux系统调用 16.21 linux-gate.so.1是什么东西 16.22 快速获取.so的导出函数 16.23 -fPIC/-fpic/-fPIE/-fpie的区别 16.24 16.25 用sprof剖析共享库 16.26 16.27 16.28 在Debian上手工升级GLIBC 17. 文件查看问题 17.0 如何改变vi临时目录 17.1 如何直接查看man文件 17.2 .tex文件怎么读 17.3 Solaris下怎么看.ps文件 17.4 如何将man手册转换成文本文件以便查看 17.5 在vi中如何忽略大小写搜索 17.6 如何用vi/xxd进行16进制编辑 17.7 vim and ctags 18. 补丁相关问题 18.0 如何在Sun主站上查询补丁信息 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 如何让终端显示从黑方块状态恢复成正常状态 19.8 minicom如何用 19.9 为何Ctrl-C未能产生SIGINT(2) 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 如何删除字符串的最后一个字符 20.20 Debian 4.0的Konsole不处理/etc/profile和~/.bash_profile 20.21 非交互模式修改用户口令 20.22 Debian自带的grep不支持-P 20.23 如何在命令行上循环执行某条命令 20.24 如何利用objdump、grep寻找"pop ret"、"pop pop ret"序列 21. BSD相关问题 21.0 21.1 21.2 如何将一个512字节的文件写入主引导扇区 21.3 21.4 21.5 21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel 21.7 x86/FreeBSD下如何设置路由 21.8 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 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访问 21.33 FreeBSD如何手工配置TCP/IP协议栈 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如何手工配置TCP/IP协议栈 23.4 如何确认是何种Unix Release 23.5 VI系列 23.6 如何产生core dump 23.7 socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错 23.8 unknown terminal "vt100" 23.9 23.10 Debian 4.0 KDE不允许root登录 23.11 23.12 Debian上如何安装配置telnet服务 23.13 Debian上如何更改locale 23.14 一个账号有两个口令可供登录 23.15 SecureCRT远程登录时如何显示汉字 23.16 telnet登录Debian时出现"Login timed out after 60 seconds" 23.17 打开bash的增强TAB扩展功能 23.18 所有用户均无法以SSH登录 23.19 Debian上如何安装配置tftp服务 23.20 23.21 Debian上如何打开、关闭服务 23.22 Debian启动时在某一包含MTA字样的行停留了很长时间 23.23 23.24 smbclient如何获取远程共享目录列表 23.25 如何查看当前物理内存大小 23.26 23.27 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()时想显示文件名及行号 24.10 如何得到当前活动连接对应的本机IP地址 24.11 C语言编程时如何在嵌入式汇编里使用宏 24.12 24.13 对于multihomed主机如何确定发往本机的UDP报文的目标IP 24.14 MIPS/Debian上嵌入式汇编中自动插入nop充任delay slot 24.15 MIPS/Debian上嵌入式汇编中使用$a0这样的寄存器名 24.16 relocation truncated to fit: R_MIPS_PC16 against `no symbol' 24.17 gcc -pthread与gcc -lpthread有什么区别 24.18 传统Unix的DES型Passwd Hash是如何生成的 24.19 24.20 24.21 TLS与动态库 24.22 GCC命令行选项 24.23 printf()格式串 24.24 /usr/include/features.h:323:26: bits/predefs.h: No such file or directory 24.25 24.26 __builtin_expect()有什么用 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如何进入单用户模式 26. Python相关问题 26.0 Python如何强制回收内存 26.1 通过管道重定向标准输入时如何保持raw read 26.2 如何计算从"1970-01-01"到某日的间隔天数 26.3 显示Python库路径 26.4 re.findall()的非贪婪模式 26.5 两个等长list之间的merge(paste)操作 26.6 测试代码性能 26.7 如何把一个字符串反序 26.8 如何确认Python的版本 26.9 如何查看某模块的源代码 26.10 如何将一段16进制字节流转换成可打印字符 27. Wireshark相关问题 27.0 如何得到IBM AppScan的规则升级包下载链接 27.1 捕捉TCP RST报文 27.2 Wireshark 1.8.x的Capture Filter在哪儿 28. WEB前端安全相关问题 28.0 如何在网页上自动提交POST请求 -------------------------------------------------------------------------- 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/ A: http://en.wikipedia.org/wiki/Dennis_Ritchie Dennis Ritchie出生于1941年9月9日,逝世于2011年10月12日,享年70岁。 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.3 更多Unix传奇故事 A: http://www.salon.com/directory/topics/unix/index.html 0.4 那些Unix传奇人物长什么样,不会都是三头六臂吧 A: 长得并没有多帅,不过更多比他们帅的人没能在Unix历史上留下什么,我想他们还是 蛮和蔼的嘛。这里是其中九位的照片: Famous Hacker & Engineer http://fsl.myetang.com/ 0.5 "3y3"是如何转换成"eye"的 A: 下面是一个基本转换表 -------------------------------------------------------------------------- a 4 @ 4 4 @ @ 4 4 4 or @ or /-\ b |3 b 8 8 B |3 8 8 8 or |3 c C c c k C ( < < ( d |) |) D d D |) D c| |) e 3 3 3 3 3 3 3 3 3 f |>|-| f ph F F |[ F |= |= or pH g 6 G g 9 6 6 6 6 9 h |-| |-| H |-| H |{ H |-| |-| or # i 1 1 i 1 ! | 1 ][ or 1 1 or | or ! j _| j j j J _| J _] J k |< |< k |< K |< [< |< |{ or |< l |_ 1 1 1 1 |_ |- 1 or | or [ or |_ |_ or []_ m |\/| |\/| M /\/\ M |V| M |\/| |\/| n |\| |\| n |\| N |\| N |\| |\| or /\/ o 0 o o 0 0 0 0 0 0 p |> p P p P |o P |> |> q Q q Q q Q O, Q 0 Q r |2 r R |2 R |)\ R |2 |2 s 5 5 s 5 5 5 $ 5 or Z 5 t 7 + + 7 7 7 7 7 or + + or 7 u |_| u u u U |_| U |_| |_| or \_/ v \/ v V \/ V \/ V \/ \/ w \/\/ \/\/ W \/\/ W |/\| W \/\/ \/\/ x >< >< X >< X X >< X y `/ Y y '/ Y \/ | Y j or J or `/ Y z Z z Z z 2 -\_ Z 5 Z -------------------------------------------------------------------------- 如果只进行字母到数字的转换,可以简化成 -------------------------------------------------------------------------- a -> 4 b -> 8 e -> 3 g -> 6 i -> 1 l -> 1 o -> 0 s -> 5 t -> 7 -------------------------------------------------------------------------- 这里有一个转换页面,由于存在一对多的现象,转换结果可能并不完全相符 http://www.planetquake.com/turkey/l33translate.htm 比如"I'm a programmer",将被转换成"1'/\/\ 4 p|209|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@nsfocus 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 Infodoc ID - 26973 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/26973 有三种方式改变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@nsfocus 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@nsfocus 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: scz@nsfocus SPARC/Solaris 7下测试 如果目录拥有SGID设置,那么该目录下新创建的文件将继承该目录的属组,而不是创 建者所对应的GID。 [root@ /export/home/scz]> id uid=0(root) gid=1(other) <-- 注意当前用户的属组 [root@ /export/home/scz]> mkdir groupsgid [root@ /export/home/scz]> ls -ld groupsgid drwxr-xr-x root other groupsgid/ [root@ /export/home/scz]> chown scz:users groupsgid [root@ /export/home/scz]> chmod g+s groupsgid [root@ /export/home/scz]> ls -ld groupsgid drwxr-sr-x scz users groupsgid/ <-- 目录拥有SGID设置 [root@ /export/home/scz]> cd groupsgid/ [root@ /export/home/scz/groupsgid]> touch scz_0 [root@ /export/home/scz/groupsgid]> ls -l scz_0 -rw-r--r-- root users scz_0 <-- 注意属组变化 [root@ /export/home/scz/groupsgid]> chmod g-s ../groupsgid/ [root@ /export/home/scz/groupsgid]> ls -ld ../groupsgid/ drwxr-xr-x scz users ../groupsgid/ [root@ /export/home/scz/groupsgid]> touch scz_1 [root@ /export/home/scz/groupsgid]> ls -l scz_1 -rw-r--r-- root other scz_1 <-- 注意属组变化 [root@ /export/home/scz/groupsgid]> 1.7 非Sun Console上有无等价Stop-A的按键 A: neomilev 如果是便携机,尝试Alt-Break或者Ctrl-Break。如果是vt100终端,尝试F11或者 Break。 1.8 如何让一个用户只能ftp而无法telnet A: scz@nsfocus 修改该用户在/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 如果之前不存在/export/home/,需要先创建它再useradd: mkdir -p /export/home/ 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 1.16 Solaris上root设置普通用户口令时提示权限否定 A: wayy@SMTH 2006-03-16 13:46:53 检查/etc/nsswitch.conf中的如下行: passwd: files 一般这里只有files。如果files之前出现了ldap、nis等其它值,就可能出现前述问 题。此时可以修正nsswitch.conf或者执行如下命令设置口令: # passwd -r files 1.17 Solaris禁止root帐号远程telnet A: vi /etc/default/login # If CONSOLE is set, root can only login on that device. # Comment this line out to allow remote login by root. # CONSOLE=/dev/console 1.18 如何关机、重启 A: 关机 init 0 halt 重启 init 6 reboot 1.19 Solaris允许root帐号远程ftp A: 参看ftpusers(4)手册页。从/etc/ftpd/ftpusers中注释或删除root帐号即可。 vi /etc/ftpd/ftpusers # # List of users denied access to the FTP server, see ftpusers(4). # # root D: scz@nsfocus 对于Solaris 8,这个文件变成了"/etc/ftpusers"。 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@nsfocus 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.2 Solaris的pstack实现源码 A: Sun Microsystems 下面是来自SPARC/Solaris 7的源码libproc.h和pstack.c -------------------------------------------------------------------------- /* * Copyright (c) 1997-1998 by Sun Microsystems, Inc. * All rights reserved. */ /* * Interfaces available from the process control library, libproc. * * libproc provides process control functions for the /proc tools * (commands in /usr/proc/bin), /usr/bin/truss, and /usr/bin/gcore. * libproc is a private support library for these commands only. * It is _not_ a public interface, although it might become one * in the fullness of time, when the interfaces settle down. * * In the meantime, be aware that any program linked with libproc in this * release of Solaris is almost guaranteed to break in the next release. * * In short, do not use this header file or libproc for any purpose. */ #ifndef _LIBPROC_H #define _LIBPROC_H #pragma ident "@(#)libproc.h 1.2 98/01/29 SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* * Opaque structure tag reference to a process control structure. * Clients of libproc cannot look inside the process control structure. * The implementation of struct ps_prochandle can change w/o affecting clients. */ struct ps_prochandle; extern int _libproc_debug; /* set non-zero to enable debugging fprintfs */ #if defined(sparc) || defined(__sparc) #define R_RVAL1 R_O0 /* register holding a function return value */ #define R_RVAL2 R_O1 /* 32 more bits for a 64-bit return value */ #define SYSCALL32 0x91d02008 /* 32-bit syscall (ta 8) instruction */ #define SYSCALL64 0x91d02040 /* 64-bit syscall (ta 64) instruction */ typedef uint32_t syscall_t; /* holds a syscall instruction */ #endif /* sparc */ #if defined(i386) || defined(__i386) #define R_PC EIP #define R_SP UESP #define R_RVAL1 EAX /* register holding a function return value */ #define R_RVAL2 EDX /* 32 more bits for a 64-bit return value */ #define SYSCALL 0x9a /* syscall (lcall) instruction opcode */ typedef u_char syscall_t[7]; /* holds a syscall instruction */ #endif /* i386 */ #define R_RVAL R_RVAL1 /* simple function return value register */ /* maximum sizes of things */ #define PRMAXSIG (32 * sizeof (sigset_t) / sizeof (uint32_t)) #define PRMAXFAULT (32 * sizeof (fltset_t) / sizeof (uint32_t)) #define PRMAXSYS (32 * sizeof (sysset_t) / sizeof (uint32_t)) /* State values returned by Pstate() */ #define PS_RUN 1 /* process is running */ #define PS_STOP 2 /* process is stopped */ #define PS_LOST 3 /* process is lost to control (EAGAIN) */ #define PS_DEAD 4 /* process is terminated */ /* Flags accepted by Pgrab() */ #define PGRAB_RETAIN 0x01 /* Retain tracing flags, else clear flags */ #define PGRAB_FORCE 0x02 /* Open the process w/o O_EXCL */ /* Error codes from Pcreate() */ #define C_STRANGE -1 /* Unanticipated error, perror() was called */ #define C_FORK 1 /* Unable to fork */ #define C_PERM 2 /* No permission (file set-id or unreadable) */ #define C_NOEXEC 3 /* Cannot find executable file */ #define C_INTR 4 /* Interrupt received while creating */ #define C_LP64 5 /* Program is _LP64, self is _ILP32 */ /* Error codes from Pgrab() */ #define G_STRANGE -1 /* Unanticipated error, perror() was called */ #define G_NOPROC 1 /* No such process */ #define G_ZOMB 2 /* Zombie process */ #define G_PERM 3 /* No permission */ #define G_BUSY 4 /* Another process has control */ #define G_SYS 5 /* System process */ #define G_SELF 6 /* Process is self */ #define G_INTR 7 /* Interrupt received while grabbing */ #define G_LP64 8 /* Process is _LP64, self is _ILP32 */ /* Flags accepted by Prelease */ #define PRELEASE_CLEAR 0x10 /* Clear all tracing flags */ #define PRELEASE_RETAIN 0x20 /* Retain final tracing flags */ #define PRELEASE_HANG 0x40 /* Leave the process stopped */ #define PRELEASE_KILL 0x80 /* Terminate the process */ typedef struct { /* argument descriptor for system call (Psyscall) */ long arg_value; /* value of argument given to system call */ void *arg_object; /* pointer to object in controlling process */ char arg_type; /* AT_BYVAL, AT_BYREF */ char arg_inout; /* AI_INPUT, AI_OUTPUT, AI_INOUT */ u_short arg_size; /* if AT_BYREF, size of object in bytes */ } argdes_t; typedef struct { /* return values from system call (Psyscall) */ int sys_errno; /* syscall error number */ long sys_rval1; /* primary return value from system call */ long sys_rval2; /* second return value from system call */ } sysret_t; /* values for type */ #define AT_BYVAL 1 #define AT_BYREF 2 /* values for inout */ #define AI_INPUT 1 #define AI_OUTPUT 2 #define AI_INOUT 3 /* maximum number of syscall arguments */ #define MAXARGS 8 /* maximum size in bytes of a BYREF argument */ #define MAXARGL (4*1024) /* Kludges to make things work on Solaris 2.6 */ #if !defined(_LP64) && !defined(PR_MODEL_UNKNOWN) #define PR_MODEL_UNKNOWN 0 #define PR_MODEL_ILP32 0 /* process data model is ILP32 */ #define PR_MODEL_LP64 2 /* process data model is LP64 */ #define PR_MODEL_NATIVE PR_MODEL_ILP32 #define pr_dmodel pr_filler[0] #define STACK_BIAS 0 #endif /* * Function prototypes for routines in the process control package. */ extern struct ps_prochandle *Pcreate(const char *, char *const *, int *, char *, size_t); extern const char *Pcreate_error(int); extern struct ps_prochandle *Pgrab(pid_t, int, int *); extern const char *Pgrab_error(int); extern int Preopen(struct ps_prochandle *); extern void Prelease(struct ps_prochandle *, int); extern void Pfree(struct ps_prochandle *); extern int Pasfd(struct ps_prochandle *); extern int Pctlfd(struct ps_prochandle *); extern int Pcreate_agent(struct ps_prochandle *); extern void Pdestroy_agent(struct ps_prochandle *); extern int Pwait(struct ps_prochandle *, u_int); extern int Pstop(struct ps_prochandle *, u_int); extern int Pstate(struct ps_prochandle *); extern pstatus_t *Pstatus(struct ps_prochandle *); extern int Pgetareg(struct ps_prochandle *, int, prgreg_t *); extern int Pputareg(struct ps_prochandle *, int, prgreg_t); extern int Psetrun(struct ps_prochandle *, int, int); extern ssize_t Pread(struct ps_prochandle *, void *, size_t, uintptr_t); extern ssize_t Pwrite(struct ps_prochandle *, const void *, size_t, uintptr_t); extern int Pclearsig(struct ps_prochandle *); extern int Pclearfault(struct ps_prochandle *); extern int Psetbkpt(struct ps_prochandle *, uintptr_t, u_long *); extern int Pdelbkpt(struct ps_prochandle *, uintptr_t, u_long); extern int Pxecbkpt(struct ps_prochandle *, u_long); extern int Psetflags(struct ps_prochandle *, long); extern int Punsetflags(struct ps_prochandle *, long); extern int Psignal(struct ps_prochandle *, int, int); extern int Pfault(struct ps_prochandle *, int, int); extern int Psysentry(struct ps_prochandle *, int, int); extern int Psysexit(struct ps_prochandle *, int, int); extern void Psetsignal(struct ps_prochandle *, const sigset_t *); extern void Psetfault(struct ps_prochandle *, const fltset_t *); extern void Psetsysentry(struct ps_prochandle *, const sysset_t *); extern void Psetsysexit(struct ps_prochandle *, const sysset_t *); extern void Psync(struct ps_prochandle *); extern sysret_t Psyscall(struct ps_prochandle *, int, u_int, argdes_t *); extern int Pisprocdir(struct ps_prochandle *, const char *); /* * Function prototypes for system calls forced on the victim process. */ extern int pr_open(struct ps_prochandle *, const char *, int, mode_t); extern int pr_creat(struct ps_prochandle *, const char *, mode_t); extern int pr_close(struct ps_prochandle *, int); extern int pr_door_info(struct ps_prochandle *, int, struct door_info *); extern void *pr_mmap(struct ps_prochandle *, void *, size_t, int, int, int, off_t); extern void *pr_zmap(struct ps_prochandle *, void *, size_t, int, int); extern int pr_munmap(struct ps_prochandle *, void *, size_t); extern int pr_memcntl(struct ps_prochandle *, caddr_t, size_t, int, caddr_t, int, int); extern int pr_sigaction(struct ps_prochandle *, int, const struct sigaction *, struct sigaction *); extern int pr_getitimer(struct ps_prochandle *, int, struct itimerval *); extern int pr_setitimer(struct ps_prochandle *, int, const struct itimerval *, struct itimerval *); extern int pr_ioctl(struct ps_prochandle *, int, int, void *, size_t); extern int pr_fcntl(struct ps_prochandle *, int, int, void *); extern int pr_stat(struct ps_prochandle *, const char *, struct stat *); extern int pr_lstat(struct ps_prochandle *, const char *, struct stat *); extern int pr_fstat(struct ps_prochandle *, int, struct stat *); extern int pr_statvfs(struct ps_prochandle *, const char *, statvfs_t *); extern int pr_fstatvfs(struct ps_prochandle *, int, statvfs_t *); extern int pr_getrlimit(struct ps_prochandle *, int, struct rlimit *); extern int pr_setrlimit(struct ps_prochandle *, int, const struct rlimit *); extern int pr_lwp_exit(struct ps_prochandle *); extern int pr_exit(struct ps_prochandle *, int); extern int pr_waitid(struct ps_prochandle *, idtype_t, id_t, siginfo_t *, int); extern off_t pr_lseek(struct ps_prochandle *, int, off_t, int); extern offset_t pr_llseek(struct ps_prochandle *, int, offset_t, int); extern int pr_rename(struct ps_prochandle *, const char *, const char *); extern int pr_link(struct ps_prochandle *, const char *, const char *); extern int pr_unlink(struct ps_prochandle *, const char *); /* * lwp iteration interface. */ typedef int proc_lwp_f(void *, const lwpstatus_t *); extern int proc_lwp_iter(struct ps_prochandle *, proc_lwp_f *, void *); /* * Symbol table interfaces. */ /* * Pseudo-names passed to proc_lookup_by_name() for well-known load objects. * NOTE: It is required that PR_OBJ_EXEC and PR_OBJ_LDSO exactly match * the definitions of PS_OBJ_EXEC and PS_OBJ_LDSO from . */ #define PR_OBJ_EXEC ((const char *)0) /* search the executable file */ #define PR_OBJ_LDSO ((const char *)1) /* search ld.so.1 */ #define PR_OBJ_EVERY ((const char *)-1) /* search every load object */ /* * 'object_name' is the name of a load object obtained from an * iteration over the process's address space mappings (proc_mapping_iter), * or an iteration over the process's mapped objects (proc_object_iter), * or else it is one of the special PR_OBJ_* values above. */ extern int proc_lookup_by_name(struct ps_prochandle *, const char *object_name, const char *sym_name, GElf_Sym *sym); extern int proc_lookup_by_addr(struct ps_prochandle *, uintptr_t addr, char *sym_name_buffer, size_t symbufsize, GElf_Sym *sym); typedef int proc_map_f(void *, const prmap_t *, const char *object_name); extern int proc_mapping_iter(struct ps_prochandle *, proc_map_f *, void *); extern int proc_object_iter(struct ps_prochandle *, proc_map_f *, void *); extern const prmap_t *proc_addr_to_map(struct ps_prochandle *, uintptr_t addr); #if 0 /* XXX not yet */ extern const prmap_t *proc_name_to_map(struct ps_prochandle *, const char *object_name); #endif extern char *proc_execname(struct ps_prochandle *, char *, size_t); extern char *proc_objname(struct ps_prochandle *, uintptr_t, char *, size_t); extern char *proc_getenv(struct ps_prochandle *, const char *, char *, size_t); /* * Symbol table iteration interface. */ typedef int proc_sym_f(void *, const GElf_Sym *sym, const char *sym_name); extern int proc_symbol_iter(struct ps_prochandle *, const char *object_name, int which, int type, proc_sym_f *, void *); /* * 'which' selects which symbol table and can be one of the following. */ #define PR_SYMTAB 1 #define PR_DYNSYM 2 /* * 'type' selects the symbols of interest by binding and type. * It is a bit-mask of one or more of the following. */ #define BIND_LOCAL 0x0001 #define BIND_GLOBAL 0x0002 #define BIND_WEAK 0x0004 #define BIND_ANY (BIND_LOCAL|BIND_GLOBAL|BIND_WEAK) #define TYPE_NOTYPE 0x0100 #define TYPE_OBJECT 0x0200 #define TYPE_FUNC 0x0400 #define TYPE_SECTION 0x0800 #define TYPE_FILE 0x1000 #define TYPE_ANY (TYPE_NOTYPE|TYPE_OBJECT|TYPE_FUNC|TYPE_SECTION|TYPE_FILE) /* * This returns the rtld_db agent handle for the process. * The handle will become invalid at the next successful exec() and * must not be used beyond that point (see proc_reset_maps(), below). */ extern rd_agent_t *proc_rd_agent(struct ps_prochandle *); /* * This should be called when an RD_DLACTIVITY event with the * RD_CONSISTENT state occurs via librtld_db's event mechanism. * This makes libproc's address space mappings and symbol tables current. */ extern void proc_update_maps(struct ps_prochandle *); /* * This must be called after the victim process performs a successful * exec() if any of the symbol table interface functions have been called * prior to that point. This is essential because an exec() invalidates * all previous symbol table and address space mapping information. * It is always safe to call, but if it is called other than after an * exec() by the victim process it just causes unnecessary overhead. * * The rtld_db agent handle obtained from a previous call to * proc_rd_agent() is made invalid by proc_reset_maps() and * proc_rd_agent() must be called again to get the new habdle. */ extern void proc_reset_maps(struct ps_prochandle *); /* * Stack frame iteration interface. */ typedef int proc_stack_f(void *, uintptr_t pc, u_int argc, const long *argv); extern int proc_stack_iter(struct ps_prochandle *, const prgregset_t, proc_stack_f *, void *); /* * Miscellaneous function prototypes. */ extern char *proc_dirname(const char *, char *, size_t); extern int proc_get_auxv(pid_t, auxv_t *, int); extern int proc_get_cred(pid_t, prcred_t *, int); extern int proc_get_psinfo(pid_t, psinfo_t *); extern int proc_get_status(pid_t, pstatus_t *); extern pid_t proc_pidarg(const char *, psinfo_t *); extern ssize_t proc_read_string(int, char *, size_t, off_t); extern char *proc_fltname(int, char *, size_t); extern char *proc_signame(int, char *, size_t); extern char *proc_sysname(int, char *, size_t); #ifdef __cplusplus } #endif #endif /* _LIBPROC_H */ -------------------------------------------------------------------------- -------------------------------------------------------------------------- /* * Copyright (c) 1994-1997 by Sun Microsystems, Inc. * All rights reserved. */ #pragma ident "@(#)pstack.c 1.12 98/01/30 SMI" #include #ifdef _LP64 #define _SYSCALL32 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *command; static int Fflag; static int is64; /* * To keep the list of user-level threads for a multithreaded process. */ struct threadinfo { struct threadinfo *next; id_t threadid; id_t lwpid; prgregset_t regs; }; static struct threadinfo *thr_head, *thr_tail; #define TRUE 1 #define FALSE 0 #define MAX_ARGS 8 static int thr_stack(const td_thrhandle_t *, void *); static void free_threadinfo(void); static id_t find_thread(id_t); static int AllCallStacks(struct ps_prochandle *, pid_t, int); static void tlhead(id_t, id_t); static int PrintFrame(void *, uintptr_t, u_int, const long *); static void PrintSyscall(lwpstatus_t *, prgregset_t); static void adjust_leaf_frame(struct ps_prochandle *, prgregset_t); static void CallStack(struct ps_prochandle *, lwpstatus_t *); static void uncontrol(char *, size_t); main(int argc, char **argv) { int retc = 0; int opt; int errflg = FALSE; if ((command = strrchr(argv[0], '/')) != NULL) command++; else command = argv[0]; /* options */ while ((opt = getopt(argc, argv, "F")) != EOF) { switch (opt) { case 'F': Fflag = PGRAB_FORCE; break; default: errflg = TRUE; break; } } argc -= optind; argv += optind; if (errflg || argc <= 0) { (void) fprintf(stderr, "usage:\t%s [-F] pid ...\n", command); (void) fprintf(stderr, " (show process call stack)\n"); (void) fprintf(stderr, " -F: force grabbing of the target process\n"); exit(2); } while (--argc >= 0) { char *arg; pid_t pid; int gcode; psinfo_t psinfo; struct ps_prochandle *Pr; /* get the specified pid and its psinfo struct */ if ((pid = proc_pidarg(arg = *argv++, &psinfo)) < 0) { (void) fprintf(stderr, "%s: no such process: %s\n", command, arg); retc++; continue; } if ((Pr = Pgrab(pid, Fflag, &gcode)) == NULL) { (void) fprintf(stderr, "%s: cannot grab %d: %s\n", command, (int)pid, Pgrab_error(gcode)); retc++; continue; } uncontrol(psinfo.pr_psargs, PRARGSZ); (void) printf("%d:\t%.70s\n", (int)pid, psinfo.pr_psargs); is64 = (psinfo.pr_dmodel == PR_MODEL_LP64); if (Pstatus(Pr)->pr_nlwp <= 1) { if (AllCallStacks(Pr, pid, FALSE) != 0) retc++; } else { int libthread; td_thragent_t *Tap; /* * First we need to get a thread agent handle. */ (void) td_init(); if (td_ta_new(Pr, &Tap) != TD_OK) /* not libthread? */ libthread = FALSE; else { /* * Iterate over all threads, calling: * thr_stack(td_thrhandle_t *Thp, NULL); * for each one to generate the list of threads. */ (void) td_ta_thr_iter(Tap, thr_stack, NULL, TD_THR_ANY_STATE, TD_THR_LOWEST_PRIORITY, TD_SIGNO_MASK, TD_THR_ANY_USER_FLAGS); (void) td_ta_delete(Tap); libthread = TRUE; } if (AllCallStacks(Pr, pid, libthread) != 0) retc++; if (libthread) free_threadinfo(); } Prelease(Pr, 0); } return (retc); } /* * Thread iteration call-back function. * Called once for each user-level thread. * Used to build the list of all threads. */ /* ARGSUSED1 */ static int thr_stack(const td_thrhandle_t *Thp, void *cd) { td_thrinfo_t thrinfo; struct threadinfo *tip; if (td_thr_get_info(Thp, &thrinfo) != TD_OK) return (0); tip = malloc(sizeof (struct threadinfo)); tip->next = NULL; tip->threadid = thrinfo.ti_tid; tip->lwpid = thrinfo.ti_lid; (void) memset(tip->regs, 0, sizeof (prgregset_t)); (void) td_thr_getgregs(Thp, tip->regs); if (thr_tail) thr_tail->next = tip; else thr_head = tip; thr_tail = tip; return (0); } static void free_threadinfo() { struct threadinfo *tip = thr_head; struct threadinfo *next; while (tip) { next = tip->next; free(tip); tip = next; } thr_head = thr_tail = NULL; } /* * Find and eliminate the thread corresponding to the given lwpid. */ static id_t find_thread(id_t lwpid) { struct threadinfo *tip; id_t threadid; for (tip = thr_head; tip; tip = tip->next) { if (lwpid == tip->lwpid) { threadid = tip->threadid; tip->threadid = 0; tip->lwpid = 0; return (threadid); } } return (0); } static int AllCallStacks(struct ps_prochandle *Pr, pid_t pid, int dothreads) { pstatus_t status; status = *Pstatus(Pr); if (status.pr_nlwp <= 1) CallStack(Pr, &status.pr_lwp); else { char lwpdirname[100]; lwpstatus_t lwpstatus; struct threadinfo *tip; int lwpfd; id_t tid; char *lp; DIR *dirp; struct dirent *dentp; (void) sprintf(lwpdirname, "/proc/%d/lwp", (int)pid); if ((dirp = opendir(lwpdirname)) == NULL) { perror("AllCallStacks(): opendir(lwp)"); return (-1); } lp = lwpdirname + strlen(lwpdirname); *lp++ = '/'; /* for each lwp */ while (dentp = readdir(dirp)) { if (dentp->d_name[0] == '.') continue; (void) strcpy(lp, dentp->d_name); (void) strcat(lp, "/lwpstatus"); if ((lwpfd = open(lwpdirname, O_RDONLY)) < 0) { perror("AllCallStacks(): open lwpstatus"); break; } else if (pread(lwpfd, &lwpstatus, sizeof (lwpstatus), (off_t)0) != sizeof (lwpstatus)) { perror("AllCallStacks(): read lwpstatus"); (void) close(lwpfd); break; } (void) close(lwpfd); /* * Find the corresponding thread, if any. */ if (dothreads) tid = find_thread(lwpstatus.pr_lwpid); else tid = 0; tlhead(tid, lwpstatus.pr_lwpid); CallStack(Pr, &lwpstatus); } (void) closedir(dirp); /* for each remaining thread w/o an lwp */ (void) memset(&lwpstatus, 0, sizeof (lwpstatus)); for (tip = thr_head; tip; tip = tip->next) { if ((tid = tip->threadid) != 0) { (void) memcpy(lwpstatus.pr_reg, tip->regs, sizeof (prgregset_t)); tlhead(tid, tip->lwpid); CallStack(Pr, &lwpstatus); } tip->threadid = 0; tip->lwpid = 0; } } return (0); } static void tlhead(id_t threadid, id_t lwpid) { if (threadid == 0 && lwpid == 0) return; (void) printf("-----------------"); if (threadid && lwpid) (void) printf(" lwp# %d / thread# %d ", (int)lwpid, (int)threadid); else if (threadid) (void) printf("--------- thread# %d ", (int)threadid); else if (lwpid) (void) printf(" lwp# %d ------------", (int)lwpid); (void) printf("--------------------\n"); } static int PrintFrame(void *cd, uintptr_t pc, u_int argc, const long *argv) { struct ps_prochandle *Pr = (struct ps_prochandle *)cd; char buff[255]; GElf_Sym sym; uintptr_t start; int length = (is64? 16 : 8); int i; (void) sprintf(buff, "%.*lx", length, (long)pc); (void) strcpy(buff + length, " ????????"); if (proc_lookup_by_addr(Pr, (uintptr_t)pc, buff + 1 + length, sizeof (buff) - 1 - length, &sym) == 0) start = sym.st_value; else start = pc; (void) printf(" %-17s (", buff); for (i = 0; i < argc && i < MAX_ARGS; i++) (void) printf((i+1 == argc)? "%lx" : "%lx, ", argv[i]); if (i != argc) (void) printf("..."); (void) printf((start != pc)? ") + %lx\n" : ")\n", (long)(pc - start)); return (0); } static void PrintSyscall(lwpstatus_t *psp, prgregset_t reg) { char sname[32]; int length = (is64? 16 : 8); u_int i; (void) proc_sysname(psp->pr_syscall, sname, sizeof (sname)); (void) printf(" %.*lx %-8s (", length, (long)reg[R_PC], sname); for (i = 0; i < psp->pr_nsysarg; i++) (void) printf((i+1 == psp->pr_nsysarg)? "%lx" : "%lx, ", (long)psp->pr_sysarg[i]); (void) printf(")\n"); } /* ARGSUSED */ static void adjust_leaf_frame(struct ps_prochandle *Pr, prgregset_t reg) { #if defined(sparc) || defined(__sparc) if (is64) reg[R_PC] = reg[R_O7]; else reg[R_PC] = (uint32_t)reg[R_O7]; reg[R_nPC] = reg[R_PC] + 4; #elif defined(i386) || defined(__i386) (void) Pread(Pr, ®[R_PC], sizeof (prgreg_t), (long)reg[R_SP]); reg[R_SP] += 4; #endif } static void CallStack(struct ps_prochandle *Pr, lwpstatus_t *psp) { prgregset_t reg; (void) memcpy(reg, psp->pr_reg, sizeof (reg)); if (psp->pr_flags & (PR_ASLEEP|PR_VFORKP)) { PrintSyscall(psp, reg); adjust_leaf_frame(Pr, reg); } (void) proc_stack_iter(Pr, reg, PrintFrame, Pr); } /* * Convert string into itself, replacing unprintable * characters with space along the way. Stop on a null character. */ static void uncontrol(char *buf, size_t n) { int c; while (n-- != 0 && (c = (*buf & 0xff)) != '\0') { if (!isprint(c)) c = ' '; *buf++ = (char)c; } *buf = '\0'; } -------------------------------------------------------------------------- 在SPARC/Solaris 7 64-bit kernel mode下编译32-bit应用程序,假设libproc.h位 于当前目录下 $ gcc -I. -O3 -o pstack pstack.c /usr/lib/libthread_db.so.1 -lproc $ /usr/ccs/bin/strip pstack 在SPARC/Solaris 7 64-bit kernel mode下编译64-bit应用程序 $ /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -I. -o pstack pstack.c \ /usr/lib/sparcv9/libthread_db.so.1 -lproc $ /usr/ccs/bin/strip pstack 之所以不使用-lthread_db链接开关,因为libthread_db.so是到libthread_db.so.0 的符号链接,我们需要的却是libthread_db.so.1。 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 ); } -------------------------------------------------------------------------- D: Diwakar Shetty 但是我不想进行多达100处修改。我已经找到了Sun的官方解决方案 http://access1.sun.com/cgi-bin/rinfo2html?313899.faq 当然,这个解决方案不可移植,幸好我只需要在Solaris中实现这个功能。 A: Sun Microsystems 2000-06-13 DOCUMENT ID: 3138-99 http://access1.sun.com/cgi-bin/rinfo2html?313899.faq 下面演示如何编程获取当前运行中线程调用栈回溯。惟一要做的就是在应用程序中调 用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@nsfocus * 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@nsfocus 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@nsfocus 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 FAQ ID - 1566 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/1566 有些时候必须得到一个运行中进程的内存映像而不能停止该进程,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: scz@nsfocus 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 ) { /* * unexpected signal, ignore */ return; } canjump = 0; fprintf( stderr, "here is on_fpe\n" ); /* * jump back to main, don't return */ siglongjmp( jmpbuf, signo ); 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 */ -------------------------------------------------------------------------- 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 ... ... (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) 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 # 2.9 在GDB里修改.text内容 Q: 用GDB加载程序,disas查看main()中代码,想用"set *
="修改代码: (gdb) set *
= Cannot access memory at address
我记得GDB是可以直接修改.text内容的。 A: scz@nsfocus 可能你没有让程序run起来,尝试下列步骤: (gdb) b *main (gdb) r (gdb) set *
= 2.10 GDB调试时碰上"Cannot insert breakpoint" Q: [root@ /root/src/samba-3.2.4/source]> uname -a Linux debian 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686 GNU/Linux [root@ /root/src/samba-3.2.4/source]> /root/src/gdb-6.8/gdb/gdb -q ./bin/smbclient (gdb) b main Breakpoint 1 at 0x61695: file client/client.c, line 4645. (gdb) r Starting program: /root/src/samba-3.2.4/source/bin/smbclient Warning: Cannot insert breakpoint 1. Error accessing memory address 0x6166d: Input/output error. A: qfp 2008-11-10 16:53 [root@ /root/src/samba-3.2.4/source]> readelf -h `which ls` ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) // 对比 Machine: Intel 80386 Version: 0x1 Entry point address: 0x8049ae0 // 对比 Start of program headers: 52 (bytes into file) Start of section headers: 76312 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 8 Size of section headers: 40 (bytes) Number of section headers: 26 Section header string table index: 25 [root@ /root/src/samba-3.2.4/source]> readelf -h ./bin/smbclient ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) // 注意 Machine: Intel 80386 Version: 0x1 Entry point address: 0x55ac0 // 注意 Start of program headers: 52 (bytes into file) Start of section headers: 9077992 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 9 Size of section headers: 40 (bytes) Number of section headers: 42 Section header string table index: 39 [root@ /root/src/samba-3.2.4/source]> 对比两个输出,smbclient的"Entry point address"不在常见的0x08NNNNNN上,应该 是编译时指定了-pie,修改Makefile去掉-pie重新编译后再试。 [root@ /root/src/samba-3.2.4/source]> make bin/smbclient [root@ /root/src/samba-3.2.4/source]> readelf -h ./bin/smbclient ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) // 对比 Machine: Intel 80386 Version: 0x1 Entry point address: 0x808ad40 // 对比 Start of program headers: 52 (bytes into file) Start of section headers: 9004324 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 9 Size of section headers: 40 (bytes) Number of section headers: 42 Section header string table index: 39 [root@ /root/src/samba-3.2.4/source]> /root/src/gdb-6.8/gdb/gdb -q ./bin/smbclient (gdb) b main Breakpoint 1 at 0x80968bd: file client/client.c, line 4645. (gdb) r Starting program: /root/src/samba-3.2.4/source/bin/smbclient Breakpoint 1, main (argc=1, argv=0xbfd3aa04) at client/client.c:4645 4645 char *base_directory = NULL; (gdb) 2.11 GDB中以Intel风格反汇编 Q: GDB反汇编时默认是AT&T风格,我不习惯。 A: (gdb) set disassembly-flavor att // AT&T风格,这是默认值 (gdb) x/4i $pc 0xb7ef48d0 : cmpl $0x0,%gs:0xc 0xb7ef48d8 : jne 0xb7ef48f9 0xb7ef48da : mov %ebx,%edx 0xb7ef48dc : mov $0x66,%eax (gdb) set disassembly-flavor intel // Intel风格 (gdb) x/4i $pc 0xb7ef48d0 : cmp DWORD PTR gs:0xc,0x0 0xb7ef48d8 : jne 0xb7ef48f9 0xb7ef48da : mov edx,ebx 0xb7ef48dc : mov eax,0x66 2.12 在GDB中调用被调试进程空间中的函数 Q: 听说可以在(gdb)提示符下直接调用memset()之类的函数。 A: (gdb) print printf( "Hello World.\n" ) Hello World. $108 = 13 (gdb) call printf( "Hello World.\n" ) // 如果返回值是void,call就不会显示返回值,print则会显示 Hello World. $109 = 13 (gdb) call (void)printf( "Hello World.\n" ) // 这是最理想的调用方式 Hello World. 2.13 在GDB中x/4i时不想看到符号式偏移 Q: (gdb) x/4i $pc 0xb7ef48d0 : cmpl $0x0,%gs:0xc 0xb7ef48d8 : jne 0xb7ef48f9 0xb7ef48da : mov %ebx,%edx 0xb7ef48dc : mov $0x66,%eax 我不想要这种显示。 A: (gdb) set print max-symbolic-offset 0 // 缺省为0,表示unlimited (gdb) x/4i $pc 0xb7ef48d0 : cmpl $0x0,%gs:0xc 0xb7ef48d8 : jne 0xb7ef48f9 0xb7ef48da : mov %ebx,%edx 0xb7ef48dc : mov $0x66,%eax (gdb) set print max-symbolic-offset 1 (gdb) x/4i $pc 0xb7ef48d0 : cmpl $0x0,%gs:0xc 0xb7ef48d8: jne 0xb7ef48f9 0xb7ef48da: mov %ebx,%edx 0xb7ef48dc: mov $0x66,%eax 暂时没有找到办法彻底禁止符号式偏移出现。 2.14 在GDB中bt查看调用栈回溯时输出太过冗长 Q: 主要是不想看到参数的具体值 A: (gdb) set print frame-arguments none // 有三种值,all/scalars/none,默认是all (gdb) bt #0 0xb7ef48d0 in sendmsg () from /lib/i686/cmov/libc.so.6 #1 0x0822d32a in doio_send (sock=..., dev=...) at socket.c:1522 #2 0x0823151a in socket_send (sock=..., dev=..., task=..., address=..., pktinfo=..., flags=...) at socket.c:4134 #3 0x08231b13 in isc_socket_sendto2 (sock=..., region=..., task=..., address=..., pktinfo=..., event=..., flags=...) at socket.c:4291 #4 0x0804c97c in client_sendpkg (client=..., buffer=...) at client.c:851 #5 0x0804ce3d in ns_client_send (client=...) at client.c:1018 #6 0x0805998f in query_send (client=...) at query.c:177 #7 0x08068e9f in query_find (client=..., event=..., qtype=...) at query.c:4868 #8 0x08062116 in query_resume (task=..., event=...) at query.c:3152 #9 0x082245da in dispatch (manager=...) at task.c:862 #10 0x08224db8 in isc__taskmgr_dispatch () at task.c:1252 #11 0x08226bea in evloop () at app.c:358 #12 0x08226e70 in isc_app_run () at app.c:550 #13 0x080593f5 in main (argc=..., argv=...) at ./main.c:932 2.15 GDB断点后处理commands中finish/until/tb带来的问题 Q: 我现在拦截recvmsg(),有几个目的: 1) 获取UDP报文的源IP、源PORT 2) 观察接收到的数据 由于recvmsg()被频繁调用,我需要设置条件断点,比如当源IP、源PORT、接收到的 数据满足一定条件时再命中断点。对于recvmsg()来说,这些信息都是函数返回之后 才能获取的,因此我设想了这样一种断点设置方式: -------------------------------------------------------------------------- b recvmsg commands silent set $x=(struct msghdr *)(*(unsigned int *)($esp+8)) finish if ($x->msg_name!=0&&$x->msg_namelen==16) set $y=(struct sockaddr_in *)$x->msg_name if ( ntohl( $y->sin_addr.s_addr )==<...>) else c end else c end end -------------------------------------------------------------------------- 但这个断点不能达到预期效果,commands中finish之后的脚本未被执行,可能是BUG, 也可能是GDB就是这样实现的。将finish换成until *(*(unsigned int *)$esp),情 况依旧。后来我试图将finish换成tb *(*(unsigned int *)$esp),并针对tb设置后 处理commands,但GDB不允许这样做: Can't use the "commands" command among a breakpoint's commands. 用define也无法绕过上述限制。 A: qfp 2009-01-07 14:20 用如下办法变通满足原始需求: -------------------------------------------------------------------------- define finish_recvmsg finish if ($x->msg_name!=0&&$x->msg_namelen==16) set $y=(struct sockaddr_in *)$x->msg_name if ( ntohl( $y->sin_addr.s_addr )==<...>) else c end else c end end -------------------------------------------------------------------------- b recvmsg commands silent set $x=(struct msghdr *)(*(unsigned int *)($esp+8)) finish_recvmsg end -------------------------------------------------------------------------- A: emacsen@SMTH 2009-01-07 12:33:14 emacsen建议用hook试试,用Google搜"gdb hook"找到类似资料: -------------------------------------------------------------------------- define hook-echo // 定义echo命令的"前处理" echo [ end define hookpost-echo // 定义echo命令的"后处理" echo ]\n end (gdb) echo Hello World [Hello World] -------------------------------------------------------------------------- 据此测试了hookpost-finish,也能满足原始需求: -------------------------------------------------------------------------- define hookpost-finish if ($x->msg_name!=0&&$x->msg_namelen==16) set $y=(struct sockaddr_in *)$x->msg_name if ( ntohl( $y->sin_addr.s_addr )==<...>) else c end else c end end -------------------------------------------------------------------------- b recvmsg commands silent set $x=(struct msghdr *)(*(unsigned int *)($esp+8)) finish end -------------------------------------------------------------------------- 2.16 在GDB里以C语言数组形式转储数据 Q: 用gdb调试一个东西,没有源码,一般我用x命令查看内存,但有时需要以C语言数组 形式对指定地址转储指定长度的数据。 A: scz@nsfocus (gdb) x/16br 0x629917D4 0x629917d4 B0 84 C4 4C 26 09 45 CE 1B 5C 56 28 F5 35 58 8D ...L&.E..\V(.5X. (gdb) set print array off (gdb) p/x *(char*)0x629917D4@16 $9 = {0xb0, 0x84, 0xc4, 0x4c, 0x26, 0x9, 0x45, 0xce, 0x1b, 0x5c, 0x56, 0x28, 0xf5, 0x35, 0x58, 0x8d} (gdb) set print array on (gdb) p/x *(char*)0x629917D4@16 $10 = {0xb0, 0x84, 0xc4, 0x4c, 0x26, 0x9, 0x45, 0xce, 0x1b, 0x5c, 0x56, 0x28, 0xf5, 0x35, 0x58, 0x8d} (gdb) set print array off (gdb) p/x *0x629917D4@4 $11 = {0xb084c44c, 0x260945ce, 0x1b5c5628, 0xf535588d} 其一般格式是: p/x *(type*)array@num 2.17 在GDB里将指定范围的内存数据转储成二进制文件 Q: windbg有一个.writemem可以将指定范围的内存数据转储成二进制文件,gdb有无内似 的命令? A: qfp 2009-05-21 16:21 对[begin, end)进行转储: dump memory append binary memory 与之相对的命令是: restore binary
2.18 如何从BIND产生的core中获知BIND的版本信息 Q: 别人发给我一个BIND产生的core文件,却没告诉我BIND的版本信息,如何获知? A: $ strings core.16107 | grep "named version:" named version: BIND 9.5.1-P3 (Aug 11 2009) 2.19 在GDB里如何搜索内存 A: scz@nsfocus 2012-01-18 14:29 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -s -o findtest findtest.c */ #define _GNU_SOURCE #include #include #include static unsigned char buf[] = "51201314"; int main ( int argc, char * argv[] ) { unsigned char *p; p = memmem( buf, sizeof( buf ), "1314", 4 ); printf( "%.*s\n", 4, p ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- $ gcc -Wall -pipe -O3 -o findtest findtest.c $ gdb -n ./findtest GNU gdb (GDB) 7.0.1-debian (gdb) b *main (gdb) r (gdb) i r esp ebp eip esp 0xbfffd47c 0xbfffd47c ebp 0xbfffd4f8 0xbfffd4f8 eip 0x80483e0 0x80483e0
(gdb) x/s &buf 0x804961c : "51201314" $ ps auwx | grep findtest root 3919 0.0 0.9 14496 7044 pts/0 S+ 14:41 0:00 gdb -n ./findtest root 3923 0.0 0.0 1532 288 pts/0 T 14:41 0:00 /tmp/findtest root 3944 0.0 0.0 3944 736 pts/1 R+ 14:43 0:00 grep findtest $ cat /proc/3923/maps 08048000-08049000 r-xp 00000000 08:01 378873 /tmp/findtest 08049000-0804a000 rw-p 00000000 08:01 378873 /tmp/findtest b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffea000-c0000000 rw-p bffea000 00:00 0 [stack] 就本例而言,至少有三种办法搜索内存。 在GDB中调用被调试进程空间中的memmem()函数: (gdb) p/x memmem( 0x08048000, 0x2000, "5120", 4 ) 0x804861c (gdb) p/x memmem( 0x08049000, 0x1000, "5120", 4 ) 0x804961c (gdb) GDB 7.x开始支持find命令: (gdb) find /b /10 0x08048000, 0x0804a000, 0x35, 0x31, 0x32, 0x30 0x804861c 0x804961c 2 patterns found. (gdb) find /b /10 0x08048000, 0x0804a000, 0x31, 0x33, 0x31, 0x34 0x8048500 0x8048620 0x8049500 0x8049620 4 patterns found. 过去的很多年里,我个人一直用define自己实现内存搜索: -------------------------------------------------------------------------- define find1 set $count=0 set $address=$arg0 while (((unsigned int)$count<(unsigned int)$arg3)&&((unsigned int)$address<=(unsigned int)$arg1)) if (*(unsigned char *)$address==(unsigned char)$arg4) set $count=$count+1 x/1bx $address end set $address=$address+$arg2 end end document find1 find1 end define find2 set $count=0 set $address=$arg0 while (((unsigned int)$count<(unsigned int)$arg3)&&((unsigned int)$address<=(unsigned int)$arg1)) if (*(unsigned short int *)$address==(unsigned short int)$arg4) set $count=$count+1 x/1hx $address end set $address=$address+$arg2 end end document find2 find2 end define find4 set $count=0 set $address=$arg0 while (((unsigned int)$count<(unsigned int)$arg3)&&((unsigned int)$address<=(unsigned int)$arg1)) if (*(unsigned int *)$address==(unsigned int)$arg4) set $count=$count+1 x/1wx $address end set $address=$address+$arg2 end end document find4 find4 end define find8 set $count=0 set $address=$arg0 while (((unsigned int)$count<(unsigned int)$arg3)&&((unsigned int)$address<=(unsigned int)$arg1)) if (*(unsigned long long *)$address==(unsigned long long)$arg4) set $count=$count+1 x/1gx $address end set $address=$address+$arg2 end end document find8 find8 end define fill1 set $count=0 set $address=$arg0 while ((unsigned int)$count<(unsigned int)$arg1) set *(unsigned char *)$address=(unsigned char)$arg2 set $address=$address+1 set $count=$count+1 end end document fill1 fill1
end define fill2 set $count=0 set $address=$arg0 while ((unsigned int)$count<(unsigned int)$arg1) set *(unsigned short int *)$address=(unsigned short int)$arg2 set $address=$address+2 set $count=$count+1 end end document fill2 fill2
end define fill4 set $count=0 set $address=$arg0 while ((unsigned int)$count<(unsigned int)$arg1) set *(unsigned int *)$address=(unsigned int)$arg2 set $address=$address+4 set $count=$count+1 end end document fill4 fill4
end define fill8 set $count=0 set $address=$arg0 while ((unsigned int)$count<(unsigned int)$arg1) set *(unsigned long long *)$address=(unsigned long long)$arg2 set $address=$address+8 set $count=$count+1 end end document fill8 fill8
end -------------------------------------------------------------------------- (gdb) help find4 find4 (gdb) help find8 find8 (gdb) find4 0x08048000 0x0804a000 4 10 0x34313331 0x8048500: 0x34313331 0x8048620: 0x34313331 0x8049500: 0x34313331 0x8049620 : 0x34313331 (gdb) find8 0x08048000 0x0804a000 4 10 0x3431333130323135 0x804861c: 0x3431333130323135 0x804961c : 0x3431333130323135 2.20 在GDB里第N次经过断点时断下来 A: 山寨办法是: -------------------------------------------------------------------------- define ignorecount set $count=0 while ($count<$arg0) set $count=$count+1 c end end -------------------------------------------------------------------------- "ignorecount "表示执行COUNT次c。 正经办法是: ignore 将忽略次断点命中,直至次断点命中。 2.21 在GDB里进行源码级调试时的常用命令 A: 对于多线程进程,查看所有线程: info threads 查看所有线程的调用栈回溯: thread apply all bt 当多线程进程出现死锁时,可以尝试用这种办法寻找原因 查看1号线程的调用栈回溯: thread apply 1 bt 切换至1号线程: thread 1 切换至2号栈帧并查看局部变量: frame 2 info locals 向高址(栈底)方向移动两层栈帧: up 2 或 frame 4 这些命令在汇编级调试中一般都用不上,但在源码级调试中很有用。 2.22 在GDB里如何查看GS:0x10的内容 Q: 逻辑地址 段选择子(16-bits):段内偏移(32-bits) 也叫远指针。程序中寻址时只能使用逻辑地址。没有办法禁用段机制,但有办法 禁用分页机制。 线性地址 逻辑地址经GDT、LDT转换后得到线性地址。 我想在GDB里查看逻辑地址"GS:0x10"的内容。 A: scz@nsfocus 在GDB里没有通用办法查看逻辑地址的内容,也没有通用办法进行逻辑地址到线性地 址的转换。 就这个具体的"GS:0x10"而言,有变通办法达到原始目的。 检查glibc-2.11.2的源码: nptl/sysdeps/i386/tls.h 从TLS_INIT_TP()宏的实现可以看出,GS的段基址对应系统调用set_thread_area()的 形参u_info->base_addr: -------------------------------------------------------------------------- int set_thread_area ( struct user_desc *u_info ); struct user_desc { unsigned int entry_number; unsigned long int base_addr; unsigned int limit; unsigned int seg_32bit:1; unsigned int contents:2; unsigned int read_exec_only:1; unsigned int limit_in_pages:1; unsigned int seg_not_present:1; unsigned int useable:1; unsigned int empty:25; }; -------------------------------------------------------------------------- GS用于访问GLIBC TLS (thread-local storage),GS段的起始地址对应tcbhead_t结 构: -------------------------------------------------------------------------- /* * nptl/sysdeps/i386/tls.h */ typedef struct { /* * Pointer to the TCB. Not necessarily the thread descriptor used by * libpthread. */ void *tcb; dtv_t *dtv; /* * Pointer to the thread descriptor. * * 指向所属结构的起始地址 */ void *self; int multiple_threads; /* * GS:0x10 */ uintptr_t sysinfo; uintptr_t stack_guard; uintptr_t pointer_guard; int gscope_flag; #ifndef __ASSUME_PRIVATE_FUTEX int private_futex; #else int __unused1; #endif /* * Reservation of some values for the TM ABI. */ void *__private_tm[5]; } tcbhead_t; -------------------------------------------------------------------------- $ gdb /usr/bin/col (gdb) catch syscall set_thread_area Catchpoint 1 (syscall 'set_thread_area' [243]) (gdb) r Starting program: /usr/bin/col Catchpoint 1 (call to syscall 'set_thread_area'), 0xb7fe6cff in ?? () from /lib/ld-linux.so.2 (gdb) x/i $eip-2 0xb7fe6cfd: int $0x80 (gdb) i r ebx ebx 0xbffffaa0 -1073743200 (gdb) x/1wx $ebx+4 0xbffffaa4: 0xb7e8a8d0 (gdb) x/1wx *(unsigned int *)($ebx+4)+0x10 0xb7e8a8e0: 0xb7fe4400 (gdb) x/17i *(unsigned int *)(*(unsigned int *)($ebx+4)+0x10) 0xb7fe4400: push %ecx 0xb7fe4401: push %edx 0xb7fe4402: push %ebp 0xb7fe4403: mov %esp,%ebp 0xb7fe4405: sysenter 0xb7fe4407: nop 0xb7fe4408: nop 0xb7fe4409: nop 0xb7fe440a: nop 0xb7fe440b: nop 0xb7fe440c: nop 0xb7fe440d: nop 0xb7fe440e: jmp 0xb7fe4403 0xb7fe4410: pop %ebp 0xb7fe4411: pop %edx 0xb7fe4412: pop %ecx 0xb7fe4413: ret (gdb) -------------------------------------------------------------------------- 拦截系统调用set_thread_area(),检查形参,获得GS段的线性地址(base_addr),将 base_addr(0xb7e8a8d0)加0x10,得到GS:0x10对应的线性地址(0xb7e8a8e0),检查该 地址内容为0xb7fe4400。 顺便说一句,0xb7fe4400即__kernel_vsyscall()。在没有ASLR机制的年代,后者对 应0xffffe400。 2.23 ELF Auxiliary Vectors D: Manu Garg 2006 参看Linux内核源码: fs/binfmt_elf.c 这里实现了ELF文件的加载。ELF文件加载时,栈被初始化成如下样子: position content size(bytes) + comment -------------------------------------------------------------------------- stack pointer -> [ argc = number of args ] 4 [ argv[0] (pointer) ] 4 (program name) [ argv[1] (pointer) ] 4 [ argv[...] (pointer) ] 4 * x [ argv[n - 1] (pointer) ] 4 [ argv[n] (pointer) ] 4 (=NULL) [ envp[0] (pointer) ] 4 [ envp[1] (pointer) ] 4 [ envp[...] (pointer) ] 4 [ envp[term] (pointer) ] 4 (=NULL) [ auxv[0] (Elf32_auxv_t) ] 8 [ auxv[1] (Elf32_auxv_t) ] 8 [ auxv[...] (Elf32_auxv_t) ] 8 [ auxv[term] (Elf32_auxv_t) ] 8 (=AT_NULL vector) [ padding ] 0~16 [ argument ASCIIZ str ] >=0 [ environment ASCIIZ str ] >=0 (0xbffffffc) [ end marker ] 4 (=NULL) (0xc0000000) < bottom of stack > 0 (virtual) -------------------------------------------------------------------------- 大多数时候C程序员这样写main(): int main ( int argc, char * argv[] ); 如果需要访问环境变量,就会这样写: int main ( int argc, char * argv[], char * envp[] ); 但在x86上没法直接访问auxv[],据说PowerPC上第4形参是auxv[]。 ------------------------------------------------------------------------ /* * /usr/include/elf.h */ typedef struct { /* * Entry type */ uint32_t a_type; union { uint32_t a_val; } a_un; } Elf32_auxv_t; /* * Legal values for a_type (entry type). */ #define AT_NULL 0 // End of vector #define AT_IGNORE 1 // Entry should be ignored #define AT_EXECFD 2 // File descriptor of program #define AT_PHDR 3 // Program headers for program #define AT_PHENT 4 // Size of program header entry #define AT_PHNUM 5 // Number of program headers #define AT_PAGESZ 6 // System page size #define AT_BASE 7 // Base address of interpreter #define AT_FLAGS 8 // Flags #define AT_ENTRY 9 // Entry point of program #define AT_NOTELF 10 // Program is not ELF #define AT_UID 11 // Real uid #define AT_EUID 12 // Effective uid #define AT_GID 13 // Real gid #define AT_EGID 14 // Effective gid #define AT_CLKTCK 17 // Frequency of times() /* * Some more special a_type values describing the hardware. */ #define AT_PLATFORM 15 // String identifying platform. #define AT_HWCAP 16 // Machine dependent hints about processor capabilities. /* * This entry gives some information about the FPU initialization performed by the kernel. */ #define AT_FPUCW 18 // Used FPU control word. /* * Cache block sizes. */ #define AT_DCACHEBSIZE 19 // Data cache block size. #define AT_ICACHEBSIZE 20 // Instruction cache block size. #define AT_UCACHEBSIZE 21 // Unified cache block size. /* * A special ignored value for PPC, used by the kernel to control the * interpretation of the AUXV. Must be > 16. */ #define AT_IGNOREPPC 22 // Entry should be ignored. #define AT_SECURE 23 // Boolean, was exec setuid-like? #define AT_BASE_PLATFORM 24 // String identifying real platforms. #define AT_RANDOM 25 // Address of 16 random bytes. #define AT_EXECFN 31 // Filename of executable. /* * Pointer to the global system page used for system calls and other nice things. */ #define AT_SYSINFO 32 #define AT_SYSINFO_EHDR 33 /* * Shapes of the caches. Bits 0-3 contains associativity; bits 4-7 contains * log2 of line size; mask those to get cache size. */ #define AT_L1I_CACHESHAPE 34 #define AT_L1D_CACHESHAPE 35 #define AT_L2_CACHESHAPE 36 #define AT_L3_CACHESHAPE 37 ------------------------------------------------------------------------ 绝大多数时候,只有ELF加载器需要关心ELF Auxiliary Vectors,程序员并不怎么关 心。但如果你非常好奇,有一个简便方法让你查看ELF Auxiliary Vectors: $ LD_SHOW_AUXV=1 /bin/true AT_SYSINFO: 0xb7fc8400 AT_SYSINFO_EHDR: 0xffffe000 AT_HWCAP: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss AT_PAGESZ: 4096 AT_CLKTCK: 100 AT_PHDR: 0x8048034 AT_PHENT: 32 AT_PHNUM: 7 AT_BASE: 0xb7fc9000 AT_FLAGS: 0x0 AT_ENTRY: 0x80489e0 AT_UID: 0 AT_EUID: 0 AT_GID: 0 AT_EGID: 0 AT_SECURE: 0 AT_PLATFORM: i686 下面演示如何编程寻找AT_SYSINFO、AT_SYSINFO_EHDR: -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o elf_auxv_demo elf_auxv_demo.c */ #include #include unsigned int get_auxv ( Elf32_auxv_t *auxv, unsigned int type ) { unsigned int value = 0xffffffff; for ( ; AT_NULL != auxv->a_type; auxv++ ) { if ( type == auxv->a_type ) { value = auxv->a_un.a_val; break; } } return( value ); } /* end of get_auxv */ int main ( int argc, char * argv[], char * envp[] ) { Elf32_auxv_t *auxv; unsigned int value; while ( NULL != *envp++ ); auxv = ( Elf32_auxv_t * )envp; if ( 0xffffffff != ( value = get_auxv( auxv, AT_SYSINFO ) ) ) { printf( "AT_SYSINFO = 0x%x\n", value ); } if ( 0xffffffff != ( value = get_auxv( auxv, AT_SYSINFO_EHDR ) ) ) { printf( "AT_SYSINFO_EHDR = 0x%x\n", value ); } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ for i in `seq 0 1 2`;do ./elf_auxv_demo;done AT_SYSINFO = 0xb7fcc400 AT_SYSINFO_EHDR = 0xffffe000 AT_SYSINFO = 0xb7f0d400 AT_SYSINFO_EHDR = 0xffffe000 AT_SYSINFO = 0xb7ee8400 AT_SYSINFO_EHDR = 0xffffe000 可以看出,AT_SYSINFO被随机化了,AT_SYSINFO_EHDR好像是无效的。禁用ASLR之后 的效果: $ for i in `seq 0 1 2`;do setarch `uname -m` -R ./elf_auxv_demo | grep "AT_SYSINFO ";done AT_SYSINFO = 0xb7fe4400 AT_SYSINFO = 0xb7fe4400 AT_SYSINFO = 0xb7fe4400 利用LD_SHOW_AUXV环境变量时的效果: $ for ((i=0;i<3;i++));do LD_SHOW_AUXV=1 ./elf_auxv_demo | grep "AT_SYSINFO[ :]";done AT_SYSINFO: 0xb7f04400 AT_SYSINFO = 0xb7f04400 AT_SYSINFO: 0xb7f48400 AT_SYSINFO = 0xb7f48400 AT_SYSINFO: 0xb7f71400 AT_SYSINFO = 0xb7f71400 D: scz@nsfocus elf_auxv_demo.c演示了一种比较繁琐的寻找auxv[]的办法,其实有更省事的办法: $ setarch `uname -m` -R cat /proc/self/auxv | xxd -g 1 | grep "20 00 00 00" 0000000: 20 00 00 00 00 44 fe b7 21 00 00 00 00 e0 ff ff ....D..!....... 0000030: 04 00 00 00 20 00 00 00 05 00 00 00 07 00 00 00 .... ........... 可以看出AT_SYSINFO等于0xb7fe4400。 2.24 如何用GDB调试子进程 Q: 如何用GDB调试子进程?这个问题被问了十几年了,上个世纪就不断地被各种人在各 种论坛上问起。 gdb提供了一条命令,set follow-fork-mode child,本意是fork()之后跟踪进子进 程。但当年这个功能是系统相关的,在x86/Linux、x86/FreeBSD、SPARC/Solaris上 无效,据说有人在HP-UX上用成功过。 hhuu@SMTH提供过一个方案: -------------------------------------------------------------------------- 1) 在fork()处设置断点,修改fork()的返回值为0,迫使父进程去走子进程的流程。这 种做法并没有真正进入已经存在的子进程空间,但对付简单情形非常有效。 2) 如果能修改源代码,就很好办了,在fork()之后的子进程流程中增加阻塞类代码,比 如sleep()、getchar()等等,然后用gdb attach调试子进程。不过一般问此问题的人 都是无法修改源代码的,说了等于没说。 -------------------------------------------------------------------------- 另有人就此问题写过一段内容: -------------------------------------------------------------------------- gdb对调试fork()产生的子进程没有很多支持。一般fork()之后,gdb继续对父进程进 行调试,子进程将不受影响地运行。如果你在子进程流程中设了断点,当断点命中时 子进程将收到SIGTRAP信号,如果子进程没有对这个信号进行处理,缺省行为就是使 子进程终止。 -------------------------------------------------------------------------- 多么悲催的历史,不知现状如何? A: zz@nsfocus 2012-05 现在x86/Linux上的gdb已经可以正常使用"set follow-fork-mode child"了。 -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o follow-fork-mode-test follow-fork-mode-test.c * gcc-3.3 -Wall -pipe -O0 -ggdb -g -o follow-fork-mode-test follow-fork-mode-test.c */ #include #include int main ( int argc, char * argv[] ) { pid_t pid; pid = fork(); if ( 0 == pid ) { /* * 子进程 */ printf( "child = %u\n", getpid() ); execl( "./none", NULL ); } else { /* * 父进程 */ printf( "parent = %u\n", getpid() ); } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ uname -a Linux debian 3.4.0-scz-20120531 #2 SMP Thu May 31 15:47:42 CST 2012 i686 GNU/Linux (gdb) show version GNU gdb (GDB) 7.0.1-debian (gdb) help set follow-fork-mode Set debugger response to a program call of fork or vfork. A fork or vfork creates a new process. follow-fork-mode can be: parent - the original process is debugged after a fork child - the new process is debugged after a fork The unfollowed process will continue to run. By default, the debugger will follow the parent process. (gdb) show follow-fork-mode Debugger response to a program call of fork or vfork is "parent". 注意,这里只支持child、parent,不支持传说中的ask。 调试父进程(这是缺省行为): -------------------------------------------------------------------------- $ gdb ./follow-fork-mode-test (gdb) catch fork Catchpoint 1 (fork) (gdb) r Starting program: /tmp/follow-fork-mode-test Catchpoint 1 (forked process 2075), 0xb7fe2424 in __kernel_vsyscall () (gdb) x/i $eip-2 0xb7fe2422 <__kernel_vsyscall+14>: int $0x80 (gdb) ni child = 2075 // 子进程已经独立执行过去了 0xb7fe2424 in __kernel_vsyscall () (gdb) i r eax eax 0x81b 2075 // fork()返回值大于0,现在是父进程 (gdb) i proc process 2072 cmdline = '/tmp/follow-fork-mode-test' cwd = '/tmp' exe = '/tmp/follow-fork-mode-test' (gdb) c Continuing. parent = 2072 Program exited normally. (gdb) q -------------------------------------------------------------------------- 调试子进程: -------------------------------------------------------------------------- $ gdb ./follow-fork-mode-test (gdb) set follow-fork-mode child (gdb) catch fork Catchpoint 1 (fork) (gdb) r Starting program: /tmp/follow-fork-mode-test Catchpoint 1 (forked process 2082), 0xb7fe2424 in __kernel_vsyscall () (gdb) x/i $eip-2 0xb7fe2422 <__kernel_vsyscall+14>: int $0x80 (gdb) ni parent = 2079 // 父进程已经独立执行过去了 [New process 2082] 0xb7fe2425 in __kernel_vsyscall () (gdb) i r eax eax 0x0 0 // fork()返回值等于0,现在是子进程 (gdb) i proc process 2082 cmdline = '/tmp/follow-fork-mode-test' cwd = '/tmp' exe = '/tmp/follow-fork-mode-test' (gdb) c Continuing. child = 2082 Program exited normally. (gdb) q -------------------------------------------------------------------------- D: scz@nsfocus 2012-06-01 发现调试follow-fork-mode-test时"catch exec"无效,"catch syscall execve"则 可以断下来。 排查后发现"catch exec"并不等同于"catch syscall execve",后者更底层。如果想 拦截fork()之后那一次exec*(),应该用后者,此时还在原进程空间中,尚未切入新 进程空间。前者断下来的时候,已经切入新进程空间。假设execve()失败,用前者根 本断不下来。 -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o pwd-test pwd-test.c * gcc-3.3 -Wall -pipe -O0 -ggdb -g -o pwd-test pwd-test.c */ #include #include int main ( int argc, char * argv[] ) { pid_t pid; pid = fork(); if ( 0 == pid ) { /* * 子进程 */ execl( "/bin/pwd", "", NULL ); } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gdb ./pwd-test (gdb) set follow-fork-mode child (gdb) catch fork Catchpoint 1 (fork) (gdb) r Starting program: /tmp/pwd-test Catchpoint 1 (forked process 2218), 0xb7fe2424 in __kernel_vsyscall () (gdb) catch syscall execve // 对比测试[0] Catchpoint 2 (syscall 'execve' [11]) (gdb) c Continuing. [New process 2218] Catchpoint 2 (call to syscall 'execve'), 0xb7fe2424 in __kernel_vsyscall () (gdb) bt // 此时还是pwd-test进程 #0 0xb7fe2424 in __kernel_vsyscall () #1 0xb7f19a3f in execve () from /lib/i686/cmov/libc.so.6 #2 0xb7fc4ff4 in ?? () from /lib/i686/cmov/libc.so.6 (gdb) x/i $eip-2 0xb7fe2422 <__kernel_vsyscall+14>: int $0x80 (gdb) c Continuing. Executing new program: /bin/pwd Catchpoint 2 (returned from syscall 'execve'), 0xb7fe3850 in ?? () from /lib/ld-linux.so.2 (gdb) bt // 此时已经是pwd进程 #0 0xb7fe3850 in ?? () from /lib/ld-linux.so.2 (gdb) x/i $eip-2 0xb7fe384e: add %al,(%eax) (gdb) c Continuing. /tmp Program exited normally. (gdb) q -------------------------------------------------------------------------- $ gdb ./pwd-test (gdb) set follow-fork-mode child (gdb) catch fork Catchpoint 1 (fork) (gdb) r Starting program: /tmp/pwd-test Catchpoint 1 (forked process 2233), 0xb7fe2424 in __kernel_vsyscall () (gdb) catch exec // 对比测试[1] Catchpoint 2 (exec) (gdb) c Continuing. [New process 2233] Executing new program: /bin/pwd Catchpoint 2 (exec'd /bin/pwd), 0xb7fe3850 in ?? () from /lib/ld-linux.so.2 (gdb) bt // 此时已经是pwd进程 #0 0xb7fe3850 in ?? () from /lib/ld-linux.so.2 (gdb) x/i $eip-2 0xb7fe384e: add %al,(%eax) (gdb) c Continuing. /tmp Program exited normally. (gdb) q -------------------------------------------------------------------------- 2.25 Debian strace(1) A: scz 2013-09-18 16:30 -------------------------------------------------------------------------- NAME strace - 跟踪系统调用和信号 SYNOPSIS strace [-dffhiqrtttTvxx] [-a column] [-e expr] ... [-o file] [-p pid] ... [-s strsize] [-u username] [-E var=val] ... [ -E var] ... [command [arg ...]] strace -c [-e expr] ... [-O overhead] [-S sortby] [command [arg ...]] DESCRIPTION strace是诊断、调试工具。 最简使用方式是"strace ",strace跟踪显示进程使用的系统调用以及 进程收到的信号。每个系统调用的名称、参数、返回值输出到stderr或由-o指定 的文件。 # strace cat /dev/null execve("/bin/cat", ["cat", "/dev/null"], [/* 18 vars */]) = 0 ... open("/dev/null", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 read(3, ""..., 4096) = 0 close(3) = 0 ... # strace cat /foo/bar ... open("/foo/bar", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) ... # strace sleep 6000 ... --- SIGINT (Interrupt) @ 0 (0) --- +++ killed by SIGINT +++ # kill -INT `ps auwx | grep "sleep 6000" | grep -v strace | grep -v grep | awk '{print $2;}'` # kill -INT `pidof -s sleep` # strace ls -l /dev/null 2>&1 | grep lstat lstat64("/dev/null", {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 # strace ls -l /foo/bar 2>&1 | grep lstat lstat64("/foo/bar", 0x806b168) = -1 ENOENT (No such file or directory) # strace ls -l 2>&1 | less ... open("/etc/nsswitch.conf", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0644, st_size=558, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7ee5000 read(4, "# /etc/nsswitch.conf\n#\n# Example "..., 4096) = 558 ... 上述显示中read()的第2参数只显示了前32字节(缺省值,可通过"-s strsize"调 整)的内容,控制字符以ESC序列显示,省略号表示后面实际还有字节未显示。不 知"ls -l"访问"/etc/nsswitch.conf"为哪般? 假设"strace id ..."得到: getgroups(32, [100, 0]) = 2 第2参数表示返回了两个GID,分别是100、0。 sigprocmask(SIG_BLOCK, [CHLD TTOU], []) = 0 第2参数表示"SIGCHLD | SIGTTOU",注意这次的方括号中间没有逗号。 sigprocmask(SIG_UNBLOCK, ~[], NULL) = 0 第2参数最前面的波浪号表示取反,~[]意即所有信号。 OPTIONS -c 给出系统调用的次数、内核态消耗时间等摘要信息,可以与-f一起使用。 # strace -c id uid=0(root) gid=0(root) groups=0(root) % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 20 read -nan 0.000000 0 1 write -nan 0.000000 0 158 135 open -nan 0.000000 0 29 close -nan 0.000000 0 1 execve -nan 0.000000 0 9 9 access -nan 0.000000 0 3 brk -nan 0.000000 0 12 munmap -nan 0.000000 0 7 mprotect -nan 0.000000 0 6 _llseek -nan 0.000000 0 33 mmap2 -nan 0.000000 0 16 15 stat64 -nan 0.000000 0 21 fstat64 -nan 0.000000 0 1 getuid32 -nan 0.000000 0 1 getgid32 -nan 0.000000 0 1 geteuid32 -nan 0.000000 0 1 getegid32 -nan 0.000000 0 2 getgroups32 -nan 0.000000 0 10 fcntl64 -nan 0.000000 0 1 set_thread_area -nan 0.000000 0 1 statfs64 -nan 0.000000 0 5 1 socket -nan 0.000000 0 4 4 connect ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 343 164 total -d 向stderr输出一些调试信息。对于最终用户来说,估计永远用不上。 # strace -d -enone id [wait(0x137f) = 2915] pid 2915 stopped, [SIGSTOP] [wait(0x57f) = 2915] pid 2915 stopped, [SIGTRAP] [wait(0x57f) = 2915] pid 2915 stopped, [SIGTRAP] [wait(0x57f) = 2915] pid 2915 stopped, [SIGTRAP] [wait(0x57f) = 2915] pid 2915 stopped, [SIGTRAP] [wait(0x137f) = 2915] pid 2915 stopped, [SIGSTOP] --- SIGSTOP (Stopped (signal)) @ 0 (0) --- [wait(0x137f) = 2915] pid 2915 stopped, [SIGSTOP] --- SIGSTOP (Stopped (signal)) @ 0 (0) --- [wait(0x57f) = 2915] ... -f 跟踪由fork(2)产生的子进程 -ff 与-o一起使用,将跟踪信息输出到filename.pid中,不能与-c一起使用。 -h 显示帮助信息 -i # strace -i id [b7f2d410] execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 ... 在各行前面显示一个EIP值。受ASLR影响,每次执行各行的EIP值都不一样。 下面是关闭ASLR时的情形: # setarch `uname -m` -R strace -i id [b7fe4410] execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 ... b7fe4410即__kernel_vsyscall()+0x10,也就是在gdb中"catch syscall ..." 断下来时的EIP值。 我觉得-i这个参数实在是鸡肋,没什么用。 或许在系统范围内关闭ASLR之后-i会有点用: # sysctl -w kernel.randomize_va_space=0 # echo 0 > /proc/sys/kernel/randomize_va_space -q 安静模式,抑制attach、detach信息。 -r # strace -r id 0.000000 execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 0.000388 brk(0) = 0x8050000 0.000180 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) ... 在各行前面显示一个相对时间戳,对应本次系统调用与前次系统调用之间的 时间差。时间单位好像是秒。 -t # strace -t id 2>&1 | more 14:40:33 execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 14:40:33 brk(0) = 0x8050000 14:40:33 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) ... 在各行前面显示一个绝对时间戳 -tt # strace -tt id 2>&1 | more 14:55:44.661827 execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 14:55:44.662415 brk(0) = 0x8050000 14:55:44.662638 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) ... 比-t多了微秒 -ttt # strace -ttt id 2>&1 | more 1379833054.308859 execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 1379833054.309407 brk(0) = 0x8050000 1379833054.309576 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) ... 前面的1379833054是自epoch以来的秒数,后面仍是微秒。 $ perl -le 'print scalar localtime 1379833054' Sun Sep 22 14:57:34 2013 -T # strace -T id 2>&1 | more execve("/usr/bin/id", ["id"], [/* 18 vars */]) = 0 <0.000116> brk(0) = 0x8050000 <0.000010> access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) <0.000015> ... 在各行尾部显示本次系统调用耗费的时间 -v 冗余模式,输出更多信息 -V 显示版本信息 # strace -V strace -- version 4.5.17 -x 若字符串内含有不可打印字符,则整个字符串以\xHH形式显示 # strace -x id 2>&1 | grep read ... read(3, "\x7f\x45\x4c\x46\x01\x01...\x00\x98"..., 512) = 512 ... -xx 与-x不同,无论字符串内是否含有不可打印字符,整个字符串均以\xHH形式 显示 # strace -xx id 2>&1 | grep 591 | grep read read(3, "\x23\x20\x54...\x6f\x66"..., 4096) = 591 # strace -x id 2>&1 | grep 591 | grep read read(3, "# This file controls the state of"..., 4096) = 591 -a column 缺省各行的=号在40列(从0计)对齐,如果=号已经位于40列之后,则自然跟随。 # strace id 2>&1 | grep access | grep preload access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) # strace -a 50 id 2>&1 | grep access | grep preload access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) -e expr expr格式如下: [qualifier=][!]value1[,value2]... qualifier取值如下: trace abbrev verbose raw signal read write 缺省qualifier是trace,value是qualifier相关的符号或数字,!表示取反。 "-e trace=open"或"-eopen"表示只跟踪open()系统调用,"-e trace=!open" 表示跟踪open()之外的其他系统调用。 value有几个特殊取值: all none file process network signal ipc desc 有时候,即使!出现在""中也被shell解释成历史命令引用,此时必须用\做 ESC处理。测了一下,''内的!仍是取反的意思。 -e trace= -e 对应一系列系统调用,缺省值为all # strace -e trace=open,close,read,write id 可以与-c一起使用 # strace -c -e trace=open,close,read,write id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 20 read -nan 0.000000 0 1 write -nan 0.000000 0 158 135 open -nan 0.000000 0 29 close ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 208 135 total # strace -ewrite id > /dev/null write(1, "uid=0(root) gid=0(root) groups=0("..., 39) = 39 -e trace=file -efile 跟踪有某个形参是文件名的所有系统调用,可以认为这是一种简写方式: -e trace=open,stat,chmod,unlink,lstat,... 有助于检查进程操作了哪些文件。如果用"-e trace=",很可能漏掉某 些系统调用。 结合-c,可以快速掌握进程用到的文件名相关的系统调用都有哪些: # strace -c -efile id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 158 135 open -nan 0.000000 0 1 execve -nan 0.000000 0 9 9 access -nan 0.000000 0 16 15 stat64 -nan 0.000000 0 1 statfs64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 185 159 total -e trace=process -eprocess 跟踪所有进程管理相关的系统调用,比如fork、wait、exec*等等。 # strace -c -eprocess /usr/bin/time -o /dev/null id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000014 14 1 wait4 0.00 0.000000 0 1 execve 0.00 0.000000 0 1 clone ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000014 3 total -e trace=network -enetwork 跟踪所有网络相关的系统调用 # strace -c -enetwork dig @8.8.4.4 a www.google.com +short +vc > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 3 1 socket ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 3 1 total # strace -c -enetwork ping -c 1 192.168.7.254 > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 2 socket -nan 0.000000 0 1 connect -nan 0.000000 0 1 getsockname -nan 0.000000 0 7 setsockopt -nan 0.000000 0 1 getsockopt -nan 0.000000 0 1 sendmsg -nan 0.000000 0 1 recvmsg ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 14 total 可以用这招快速确定在gdb中针对哪些网络相关的系统调用下断点 -e trace=signal -esignal 跟踪所有信号相关的系统调用 # strace -c -esignal ping -c 1 192.168.7.254 > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 3 rt_sigaction ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 3 total -e trace=ipc -eipc 跟踪所有IPC相关的系统调用 -e trace=desc -edesc 跟踪所有文件句柄(不是文件名)相关的系统调用 # strace -c -edesc id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 20 read -nan 0.000000 0 1 write -nan 0.000000 0 158 135 open -nan 0.000000 0 29 close -nan 0.000000 0 1 1 ioctl -nan 0.000000 0 6 _llseek -nan 0.000000 0 21 fstat64 -nan 0.000000 0 10 fcntl64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 246 136 total -e abbrev= 对应一系列系统调用,缺省值为all,冗余模式(-v)暗含"-e abbrev=none" 以缩略模式显示指定系统调用所涉及的结构 # strace -e trace=stat64 id 2>&1 | grep st_size stat64("/usr/local/lib", {st_mode=S_IFDIR|S_ISGID|0775, st_size=4096, ...}) = 0 # strace -e trace=stat64 -e abbrev=none id 2>&1 | grep st_size stat64("/usr/local/lib", {st_dev=makedev(8, 1), st_ino=50353, st_mode=S_IFDIR|S_ISGID|0775, st_nlink=8, st_uid=0, st_gid=50, st_blksize=4096, st_blocks=8, st_size=4096, st_atime=2013/08/30-18:41:24, st_mtime=2012/01/18-13:40:35, st_ctime=2012/01/18-13:40:35}) = 0 -e verbose= 对应一系列系统调用,缺省值为all。 显示结构成员,而不是显示结构指针 # strace -e trace=stat64 id 2>&1 | grep "= 0" stat64("/usr/local/lib", {st_mode=S_IFDIR|S_ISGID|0775, st_size=4096, ...}) = 0 # strace -e trace=stat64 -e verbose=none id 2>&1 | grep "= 0" stat64("/usr/local/lib", 0xbfc6d370) = 0 -e raw= 对应一系列系统调用。 以16进制直接显示参数,不做任何解码、结构解引用 # strace -e trace=stat64 id 2>&1 | grep "= 0" stat64("/usr/local/lib", {st_mode=S_IFDIR|S_ISGID|0775, st_size=4096, ...}) = 0 # strace -e trace=stat64 -e raw=stat64 id 2>&1 | grep "= 0" stat64(0xbf8c9f50, 0xbf8c9fd0) = 0 -e signal= 对应一系列信号,缺省值为all。 "-e signal=!SIGIO"或"-e signal=!io"表示不跟踪SIGIO。 # kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX # strace -enone -e signal=INT sleep 6000 --- SIGINT (Interrupt) @ 0 (0) --- +++ killed by SIGINT +++ # kill -INT `ps auwx | grep "sleep 6000" | grep -v strace | grep -v grep | awk '{print $2;}'` # kill -INT `pidof -s sleep` -e read= 对应一系列文件句柄,对从指定文件句柄读取的内容进行16进制转储。 # strace -e trace=read -e read=3 id > /dev/null ... read(3, ";\376\263"..., 3) = 3 | 00000 3b fe b3 ;.. | read(3, "# This file controls the state of"..., 4096) = 591 | 00000 23 20 54 68 69 73 20 66 69 6c 65 20 63 6f 6e 74 # This f ile cont | | 00010 72 6f 6c 73 20 74 68 65 20 73 74 61 74 65 20 6f rols the state o | | 00020 66 20 53 45 4c 69 6e 75 78 20 6f 6e 20 74 68 65 f SELinu x on the | | 00030 20 73 79 73 74 65 6d 2e 0a 23 20 53 45 4c 49 4e system. .# SELIN | | 00040 55 58 3d 20 63 61 6e 20 74 61 6b 65 20 6f 6e 65 UX= can take one | | 00050 20 6f 66 20 74 68 65 73 65 20 74 68 72 65 65 20 of thes e three | | 00060 76 61 6c 75 65 73 3a 0a 23 20 65 6e 66 6f 72 63 values:. # enforc | | 00070 69 6e 67 20 2d 20 53 45 4c 69 6e 75 78 20 73 65 ing - SE Linux se | | 00080 63 75 72 69 74 79 20 70 6f 6c 69 63 79 20 69 73 curity p olicy is | | 00090 20 65 6e 66 6f 72 63 65 64 2e 0a 23 20 70 65 72 enforce d..# per | | 000a0 6d 69 73 73 69 76 65 20 2d 20 53 45 4c 69 6e 75 missive - SELinu | | 000b0 78 20 70 72 69 6e 74 73 20 77 61 72 6e 69 6e 67 x prints warning | | 000c0 73 20 69 6e 73 74 65 61 64 20 6f 66 20 65 6e 66 s instea d of enf | | 000d0 6f 72 63 69 6e 67 2e 0a 23 20 64 69 73 61 62 6c orcing.. # disabl | | 000e0 65 64 20 2d 20 4e 6f 20 53 45 4c 69 6e 75 78 20 ed - No SELinux | | 000f0 70 6f 6c 69 63 79 20 69 73 20 6c 6f 61 64 65 64 policy i s loaded | | 00100 2e 0a 53 45 4c 49 4e 55 58 3d 70 65 72 6d 69 73 ..SELINU X=permis | | 00110 73 69 76 65 0a 23 20 53 45 4c 49 4e 55 58 54 59 sive.# S ELINUXTY | | 00120 50 45 3d 20 63 61 6e 20 74 61 6b 65 20 6f 6e 65 PE= can take one | | 00130 20 6f 66 20 74 68 65 73 65 20 74 77 6f 20 76 61 of thes e two va | | 00140 6c 75 65 73 3a 0a 23 20 72 65 66 70 6f 6c 69 63 lues:.# refpolic | | 00150 79 2d 74 61 72 67 65 74 65 64 20 2d 20 4f 6e 6c y-target ed - Onl | | 00160 79 20 74 61 72 67 65 74 65 64 20 6e 65 74 77 6f y target ed netwo | | 00170 72 6b 20 64 61 65 6d 6f 6e 73 20 61 72 65 20 70 rk daemo ns are p | | 00180 72 6f 74 65 63 74 65 64 2e 0a 23 20 72 65 66 70 rotected ..# refp | | 00190 6f 6c 69 63 79 2d 73 74 72 69 63 74 20 20 20 2d olicy-st rict - | | 001a0 20 46 75 6c 6c 20 53 45 4c 69 6e 75 78 20 70 72 Full SE Linux pr | | 001b0 6f 74 65 63 74 69 6f 6e 2e 0a 23 20 72 65 66 70 otection ..# refp | | 001c0 6f 6c 69 63 79 2d 73 72 63 20 20 20 20 20 20 2d olicy-sr c - | | 001d0 20 43 75 73 74 6f 6d 20 70 6f 6c 69 63 79 20 62 Custom policy b | | 001e0 75 69 6c 74 20 66 72 6f 6d 20 73 6f 75 72 63 65 uilt fro m source | | 001f0 0a 53 45 4c 49 4e 55 58 54 59 50 45 3d 72 65 66 .SELINUX TYPE=ref | | 00200 70 6f 6c 69 63 79 2d 74 61 72 67 65 74 65 64 0a policy-t argeted. | | 00210 0a 23 20 53 45 54 4c 4f 43 41 4c 44 45 46 53 3d .# SETLO CALDEFS= | | 00220 20 43 68 65 63 6b 20 6c 6f 63 61 6c 20 64 65 66 Check l ocal def | | 00230 69 6e 69 74 69 6f 6e 20 63 68 61 6e 67 65 73 0a inition changes. | | 00240 53 45 54 4c 4f 43 41 4c 44 45 46 53 3d 30 0a SETLOCAL DEFS=0. | read(3, ""..., 4096) = 0 ... -e write= 对应一系列文件句柄,对向指定文件句柄写入的内容进行16进制转储。 # strace -e trace=write -e write=1 id > /dev/null write(1, "uid=0(root) gid=0(root) groups=0("..., 39) = 39 | 00000 75 69 64 3d 30 28 72 6f 6f 74 29 20 67 69 64 3d uid=0(ro ot) gid= | | 00010 30 28 72 6f 6f 74 29 20 67 72 6f 75 70 73 3d 30 0(root) groups=0 | | 00020 28 72 6f 6f 74 29 0a (root). | -o filename 将跟踪信息输出到filename中而不是stderr,如果与-ff一起使用,输出文件 变成filename.pid。 # strace -ff -o strace_id id uid=0(root) gid=0(root) groups=0(root) # ls -l strace_id* -rw-r--r-- 1 root root 24641 09-22 10:53 strace_id.12385 如果filename以|或!开头,剩余部分被视作一条命令,strace的输出转向到 该命令的输入。 # strace -o "| cat > test_0.txt" id # strace -o "! cat > test_1.txt" id -O overhead 与-c一起使用 overhead的单位是微秒,缺省值为1,好像是说"测量某个系统调用耗费的时 间"这件事本身要消耗1微秒,-c显示的时间已经将这1微秒刨除了。 # strace -efile -c /usr/bin/time -o /dev/null id > /dev/null # strace -efile -O 1 -c /usr/bin/time -o /dev/null id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 36 32 open -nan 0.000000 0 1 execve -nan 0.000000 0 3 3 access -nan 0.000000 0 16 15 stat64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 56 50 total 对比下面两组输出: # strace -efile -O 0 -c /usr/bin/time -o /dev/null id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 64.29 0.000036 1 36 32 open 28.57 0.000016 1 16 15 stat64 5.36 0.000003 1 3 3 access 1.79 0.000001 1 1 execve ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000056 56 50 total # strace -efile -O 2 -c /usr/bin/time -o /dev/null id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 108.55 0.000254 7 36 32 open -0.43 -0.000001 -1 1 execve -1.28 -0.000003 0 3 3 access -6.84 -0.000016 0 16 15 stat64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000234 56 50 total 结合"/usr/bin/time "、"strace -O 0 -c ..."的输出确定合理 的overhead。 其实overhead的缺省值不是固定的1,而是启发式猜测得到的一个值。 -p pid Attach到指定进程并跟踪。Ctrl-C结束跟踪,strace自动Detach。可以指定 多个"-p pid",最多32个。 指定多个"-p pid"时,一般配合使用"-ff -o ...",否则输出太混乱。 -s strsize # strace -eread id 2>&1 | grep 2570 read(3, "# Locale name alias data base.\n# "..., 4096) = 2570 上述显示中read()的第2参数只显示了前32字节的内容,这是缺省尺寸,省 略号表示后面实际还有字节未显示。可通过"-s strsize"调整这个行为: # strace -s 64 -eread id 2>&1 | grep 2570 read(3, "# Locale name alias data base.\n# Copyright (C) 1996-2001,2003,200"..., 4096) = 2570 文件名不受strsize的限制,始终完整显示。 -S sortby 与-c一起使用,sortby取值如下: time calls name nothing 缺省值为time。time、calls都是降序显示,name是升序显示,nothing表示 不排序。 # strace -O 0 -c id > /dev/null # strace -S time -O 0 -c id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 39.70 0.000158 1 158 135 open 15.33 0.000061 9 7 mprotect 8.29 0.000033 1 33 mmap2 7.29 0.000029 1 29 close 5.28 0.000021 1 21 fstat64 5.03 0.000020 1 20 read 4.02 0.000016 1 16 15 stat64 3.02 0.000012 1 12 munmap 2.51 0.000010 1 10 fcntl64 2.26 0.000009 1 9 9 access 1.51 0.000006 1 6 _llseek 1.26 0.000005 1 5 1 socket 1.01 0.000004 1 4 4 connect 0.75 0.000003 1 3 brk 0.50 0.000002 1 2 getgroups32 0.25 0.000001 1 1 write 0.25 0.000001 1 1 execve 0.25 0.000001 1 1 1 ioctl 0.25 0.000001 1 1 getuid32 0.25 0.000001 1 1 getgid32 0.25 0.000001 1 1 geteuid32 0.25 0.000001 1 1 getegid32 0.25 0.000001 1 1 set_thread_area 0.25 0.000001 1 1 statfs64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000398 344 165 total # strace -S calls -c id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 158 135 open -nan 0.000000 0 33 mmap2 -nan 0.000000 0 29 close -nan 0.000000 0 21 fstat64 -nan 0.000000 0 20 read -nan 0.000000 0 16 15 stat64 -nan 0.000000 0 12 munmap -nan 0.000000 0 10 fcntl64 -nan 0.000000 0 9 9 access -nan 0.000000 0 7 mprotect -nan 0.000000 0 6 _llseek -nan 0.000000 0 5 1 socket -nan 0.000000 0 4 4 connect -nan 0.000000 0 3 brk -nan 0.000000 0 2 getgroups32 -nan 0.000000 0 1 write -nan 0.000000 0 1 execve -nan 0.000000 0 1 1 ioctl -nan 0.000000 0 1 getuid32 -nan 0.000000 0 1 getgid32 -nan 0.000000 0 1 geteuid32 -nan 0.000000 0 1 getegid32 -nan 0.000000 0 1 set_thread_area -nan 0.000000 0 1 statfs64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 344 165 total # strace -S name -c id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 6 _llseek -nan 0.000000 0 9 9 access -nan 0.000000 0 3 brk -nan 0.000000 0 29 close -nan 0.000000 0 4 4 connect -nan 0.000000 0 1 execve -nan 0.000000 0 10 fcntl64 -nan 0.000000 0 21 fstat64 -nan 0.000000 0 1 getegid32 -nan 0.000000 0 1 geteuid32 -nan 0.000000 0 1 getgid32 -nan 0.000000 0 2 getgroups32 -nan 0.000000 0 1 getuid32 -nan 0.000000 0 1 1 ioctl -nan 0.000000 0 33 mmap2 -nan 0.000000 0 7 mprotect -nan 0.000000 0 12 munmap -nan 0.000000 0 158 135 open -nan 0.000000 0 20 read -nan 0.000000 0 1 set_thread_area -nan 0.000000 0 5 1 socket -nan 0.000000 0 16 15 stat64 -nan 0.000000 0 1 statfs64 -nan 0.000000 0 1 write ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 344 165 total # strace -S nothing -O 0 -c id > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 5.81 0.000020 1 20 read 0.29 0.000001 1 1 write 45.93 0.000158 1 158 135 open 8.43 0.000029 1 29 close 0.29 0.000001 1 1 execve 2.62 0.000009 1 9 9 access 0.87 0.000003 1 3 brk 0.29 0.000001 1 1 1 ioctl 3.49 0.000012 1 12 munmap 2.03 0.000007 1 7 mprotect 1.74 0.000006 1 6 _llseek 9.59 0.000033 1 33 mmap2 4.65 0.000016 1 16 15 stat64 6.10 0.000021 1 21 fstat64 0.29 0.000001 1 1 getuid32 0.29 0.000001 1 1 getgid32 0.29 0.000001 1 1 geteuid32 0.29 0.000001 1 1 getegid32 0.58 0.000002 1 2 getgroups32 2.91 0.000010 1 10 fcntl64 0.29 0.000001 1 1 set_thread_area 0.29 0.000001 1 1 statfs64 1.45 0.000005 1 5 1 socket 1.16 0.000004 1 4 4 connect ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000344 344 165 total -u username 以指定username执行程序并跟踪之。 # strace -o /dev/null id uid=0(root) gid=0(root) groups=0(root) # strace -o /dev/null -u scz id uid=1000(scz) gid=1000(scz) groups=20(dialout),24(cdrom),25(floppy),29(audio),44(video),46(plugdev),106(netdev),109(powerdev),1000(scz) -E var=val 在环境变量中增加"var=val"后执行程序并跟踪之。 -E var 从环境变量中删除"var"后执行程序并跟踪之。 SEE ALSO ltrace(1), time(1), ptrace(2), proc(5) BUGS 被跟踪程序是setuid程序时,setuid无效 被跟踪进程忽略SIGSTOP信号,除非在SVR4平台上 在Linux上无法跟踪init进程 -------------------------------------------------------------------------- 2.26 Debian ltrace(1) A: scz 2013-09-24 14:03 -------------------------------------------------------------------------- NAME ltrace - 跟踪库函数调用 SYNOPSIS ltrace [-CfhiLrStttV] [-a column] [-A maxelts] [-D level] [-e expr] [-l filename] [-n nr] [-o filename] [-p pid] ... [-s strsize] [-u username] [-X extern] [-x extern] ... [--align=column] [--debug=level] [--demangle] [--help] [--indent=nr] [--library=filename] [--output=filename] [--version] [command [arg ...]] DESCRIPTION 最简使用方式是"ltrace ",ltrace跟踪显示进程调用的库函数以及进 程收到的信号,也可以跟踪显示进程使用的系统调用。 ltrace的用法与strace(1)非常类似。 OPTIONS -a, --align column 缺省各行的=号在屏幕宽度的5/8处对齐 # ltrace -a 0 -e geteuid -i id > /dev/null [0x80493e7] geteuid() = 0 # ltrace -a 32 -e geteuid -i id > /dev/null [0x80493e7] geteuid() = 0 -A maxelts Maximum number of array elements to print before suppressing the rest with an ellipsis ("...") 不清楚什么时候会用到-A -c 给出库函数调用的次数、消耗时间等摘要信息 # ltrace -c id > /dev/null % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 53.36 0.001977 1977 1 getpwuid 11.66 0.000432 216 2 getgrgid 7.50 0.000278 278 1 dcgettext 4.89 0.000181 30 6 printf 3.72 0.000138 138 1 is_selinux_enabled 3.62 0.000134 67 2 sysconf 3.35 0.000124 124 1 setlocale 2.32 0.000086 43 2 fclose 1.51 0.000056 28 2 getgroups 1.05 0.000039 19 2 __fpending 0.78 0.000029 29 1 getegid 0.78 0.000029 29 1 getuid 0.78 0.000029 29 1 geteuid 0.78 0.000029 29 1 getgid 0.59 0.000022 22 1 getopt_long 0.57 0.000021 21 1 __cxa_atexit 0.57 0.000021 21 1 bindtextdomain 0.54 0.000020 20 1 free 0.54 0.000020 20 1 textdomain 0.54 0.000020 20 1 malloc 0.54 0.000020 20 1 fputs_unlocked ------ ----------- ----------- --------- -------------------- 100.00 0.003705 31 total -C, --demangle 让C++函数名更可读 -D, --debug level 向stderr输出一些调试信息。level可以是下列值(8进制)的按位或: 01 DEBUG_GENERAL Shows helpful progress information 010 DEBUG_EVENT Shows every event received by a traced program 020 DEBUG_PROCESS Shows every action ltrace carries upon a traced process 040 DEBUG_FUNCTION Shows every entry to internal functions 对于最终用户来说,估计永远用不上。 -e expr expr格式如下: [!]value1[,value2]... value对应库函数名,!表示取反。 "-e printf"表示只跟踪printf()库函数调用,"-e !printf"表示跟踪printf() 之外的其他库函数调用。 有时候,即使!出现在""中也被shell解释成历史命令引用,此时必须用\做 ESC处理。测了一下,''内的!仍是取反的意思。 -f 跟踪由fork(2)、clone(2)产生的子进程 -F filename 通常配置文件是/etc/ltrace.conf或~/.ltrace.conf,-F用于指定其他配置 文件。 配置文件中有函数原型。 -h, --help 显示帮助信息 -i 在各行前面显示一个EIP值 # ltrace -a 0 -e geteuid -i id > /dev/null [0x80493e7] geteuid() = 0 ... 0x80493e7的前一条指令就是"call geteuid"。为啥不直接显示0x80493e2? 用IDA反汇编/usr/bin/id验证之。 -l, --library filename 只显示指定库文件中的符号名。可以指定多个"-l filename",最多30个。 # ldd `which id` linux-gate.so.1 => (0xffffe000) libselinux.so.1 => /lib/libselinux.so.1 (0xb7f88000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7e42000) libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7e3d000) /lib/ld-linux.so.2 (0x80000000) # ltrace -a 50 -l /lib/ld-linux.so.2 id > /dev/null malloc(4) = 0x08052aa0 free(0x08052aa0) = # ltrace -a 50 -l /lib/libselinux.so.1 id > /dev/null is_selinux_enabled(0, 0xb7de83a5, 255, 4096, 0) = 0 # ltrace -a 50 -l /lib/ld-linux.so.2 -l /lib/libselinux.so.1 id > /dev/null is_selinux_enabled(0, 0xb7e623a5, 255, 4096, 0) = 0 malloc(4) = 0x08052aa0 free(0x08052aa0) = 显然这里只考虑动态链接库(.so),与静态链接库(.a)无关。 -L 与-S一起使用,表示不跟踪库函数调用、只跟踪系统调用 -n, --indent nr 以层层缩进的树型格式显示,体现库函数之间的主调、被调、顺序执行关系。 # ltrace -i -n 4 id > /dev/null [0x8048e81] __libc_start_main(0x80490f0, 1, 0xbfb3c0a4, 0x804d190, 0x804d180 [0x804910e] is_selinux_enabled(0, 0xb7e6c3a5, 255, 4096, 0) = 0 [0x8049134] setlocale(6, "") = "zh_CN.GBK" [0x8049148] bindtextdomain("coreutils", "/usr/share/locale") = "/usr/share/locale" [0x8049154] textdomain("coreutils") = "coreutils" [0x804d227] __cxa_atexit(0x8049d00, 0, 0, 0xb7f7eff4, 0xbfb3bfe8) = 0 [0x8049194] getopt_long(1, 0xbfb3c0a4, "agnruGZ", 0x0804d860, NULL) = -1 [0x80493e7] geteuid() = 0 [0x80493f1] getuid() = 0 [0x80493fb] getegid() = 0 [0x8049405] getgid() = 0 [0x804968c] printf("uid=%lu", 0) = 5 [0x8049699] getpwuid(0, 0, 0x804d7ae, 0x804d860, 0) = 0xb7f80c40 [0x80496b1] printf("(%s)", "root") = 6 [0x80496c6] printf(" gid=%lu", 0) = 6 [0x80496d3] getgrgid(0, 0, 0x804d7ae, 0x804d860, 0) = 0xb7f80b7c [0x80496e9] printf("(%s)", "root") = 6 [0x8049e76] getgroups(0, 0, 0xbfb3bf98, 0xb7e841f0, 0xbfb3bf88) = 1 [0x8049e26] malloc(4) = 0x08052aa0 [0x8049e8c] getgroups(1, 0x8052aa0, 0xbfb3bf98, 0xb7e841f0, 0xbfb3bf88) = 1 [0x80497a0] sysconf(3, 0, 0xbfb3bfd4, 0x804d860, 0) = 65536 [0x80497b0] sysconf(3, 0, 0xbfb3bfd4, 0x804d860, 0) = 65536 [0x80499ca] dcgettext(0, 0x804d814, 5, 0x804d860, 0) = 0x804d814 [0x80499d6] fputs_unlocked(0x804d814, 0xb7f7f4c0, 5, 0x804d860, 0) = 1 [0x80499f5] printf("%lu", 0) = 1 [0x8049a03] getgrgid(0, 0, 5, 0x804d860, 0) = 0xb7f80b7c [0x8049a19] printf("(%s)", "root") = 6 [0x8049800] free(0x08052aa0) = [0x804945d] exit(0 [0x804aeca] __fpending(0xb7f7f4c0, 1024, 0xb7e3768c, 0xb7f7eff4, 0xb7f7fcc0) = 39 [0x804aed9] fclose(0xb7f7f4c0) = 0 [0x804aeca] __fpending(0xb7f7f560, 1024, 0xb7e3768c, 0xb7f7eff4, 0xb7f7fcc0) = 0 [0x804aed9] fclose(0xb7f7f560) = 0 -o, --output filename 将跟踪信息输出到filename中而不是stderr -p pid Attach到指定进程并跟踪。Ctrl-C结束跟踪,ltrace自动Detach。 -r # ltrace -r id 0.000000 __libc_start_main(0x80490f0, 1, 0xbfa2df94, 0x804d190, 0x804d180 0.001449 is_selinux_enabled(0, 0xb7dff3a5, 255, 4096, 0) = 0 0.002092 setlocale(6, "") = "zh_CN.GBK" ... 在各行前面显示一个相对时间戳,对应本次库函数调用与前次库函数调用之 间的时间差。时间单位好像是秒。 -s strsize # ltrace -e fwrite -o ltrace.txt ping 2> /dev/null # cat ltrace.txt fwrite("Usage: ping [-LRUbdfnqrvVaA] [-c"..., 1, 262, 0xb7f43560) = 262 上述显示中fwrite()的第1参数只显示了前32字节的内容,这是缺省尺寸,省 略号表示后面实际还有字节未显示。可通过"-s strsize"调整这个行为: # ltrace -s 64 -e fwrite -o ltrace.txt ping 2> /dev/null # cat ltrace.txt fwrite("Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadli"..., 1, 262, 0xb7f86560) = 262 -S 跟踪库函数调用的同时也跟踪系统调用 # ltrace -c pwd > /dev/null % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 39.53 0.000219 219 1 setlocale 15.70 0.000087 43 2 fclose 11.91 0.000066 66 1 puts 7.22 0.000040 40 1 getcwd 7.04 0.000039 19 2 __fpending 4.15 0.000023 23 1 bindtextdomain 3.97 0.000022 22 1 getopt_long 3.61 0.000020 20 1 textdomain 3.43 0.000019 19 1 free 3.43 0.000019 19 1 __cxa_atexit ------ ----------- ----------- --------- -------------------- 100.00 0.000554 12 total 指定-S后,多了14个SYS_*,这些是系统调用,不是库函数调用 # ltrace -S -c pwd > /dev/null % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 19.87 0.000251 251 1 setlocale 19.32 0.000244 6 36 SYS_open 8.87 0.000112 14 8 SYS_mmap2 7.84 0.000099 49 2 fclose 7.76 0.000098 6 16 SYS_stat64 7.05 0.000089 89 1 puts 3.96 0.000050 50 1 getcwd 2.93 0.000037 18 2 __fpending 2.45 0.000031 5 6 SYS_close 2.30 0.000029 14 2 SYS_read 1.90 0.000024 24 1 bindtextdomain 1.74 0.000022 5 4 SYS_fstat64 1.74 0.000022 22 1 getopt_long 1.74 0.000022 7 3 SYS_access 1.58 0.000020 20 1 free 1.58 0.000020 20 1 textdomain 1.50 0.000019 19 1 __cxa_atexit 1.43 0.000018 9 2 SYS_munmap 1.43 0.000018 6 3 SYS_brk 1.19 0.000015 7 2 SYS_mprotect 0.55 0.000007 7 1 SYS_getcwd 0.48 0.000006 6 1 SYS_write 0.40 0.000005 5 1 SYS_ioctl 0.40 0.000005 5 1 SYS_set_thread_area ------ ----------- ----------- --------- -------------------- 100.00 0.001263 98 total 与strace的输出进行对比,后者有execve,前者没有SYS_execve # strace -c pwd > /dev/null % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- -nan 0.000000 0 2 read -nan 0.000000 0 1 write -nan 0.000000 0 36 32 open -nan 0.000000 0 6 close -nan 0.000000 0 1 execve -nan 0.000000 0 3 3 access -nan 0.000000 0 3 brk -nan 0.000000 0 1 1 ioctl -nan 0.000000 0 2 munmap -nan 0.000000 0 2 mprotect -nan 0.000000 0 1 getcwd -nan 0.000000 0 8 mmap2 -nan 0.000000 0 16 15 stat64 -nan 0.000000 0 4 fstat64 -nan 0.000000 0 1 set_thread_area ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 87 51 total 如果想让ltrace只跟踪系统调用(这不是常规需求,属于BT) # ltrace -e nonexist -S -c pwd > /dev/null % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 39.15 0.000249 6 36 SYS_open 15.25 0.000097 6 16 SYS_stat64 12.74 0.000081 20 4 SYS_fstat64 10.38 0.000066 8 8 SYS_mmap2 4.87 0.000031 5 6 SYS_close 3.30 0.000021 7 3 SYS_access 2.99 0.000019 9 2 SYS_read 2.83 0.000018 6 3 SYS_brk 2.67 0.000017 8 2 SYS_munmap 2.36 0.000015 7 2 SYS_mprotect 0.94 0.000006 6 1 SYS_write 0.94 0.000006 6 1 SYS_getcwd 0.79 0.000005 5 1 SYS_ioctl 0.79 0.000005 5 1 SYS_set_thread_area ------ ----------- ----------- --------- -------------------- 100.00 0.000636 86 total "-e nonexist"是山寨搞法,"-L"表示不跟踪库函数调用 # ltrace -L -S -c pwd > /dev/null % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 49.77 0.000329 9 36 SYS_open 14.83 0.000098 6 16 SYS_stat64 10.14 0.000067 8 8 SYS_mmap2 4.54 0.000030 5 6 SYS_close 3.33 0.000022 5 4 SYS_fstat64 3.18 0.000021 7 3 SYS_access 3.03 0.000020 10 2 SYS_read 2.87 0.000019 6 3 SYS_brk 2.72 0.000018 9 2 SYS_munmap 1.97 0.000013 6 2 SYS_mprotect 1.06 0.000007 7 1 SYS_getcwd 0.91 0.000006 6 1 SYS_ioctl 0.91 0.000006 6 1 SYS_write 0.76 0.000005 5 1 SYS_set_thread_area ------ ----------- ----------- --------- -------------------- 100.00 0.000661 86 total -t # ltrace -t id 15:57:21 __libc_start_main(0x80490f0, 1, 0xbfaf7854, 0x804d190, 0x804d180 15:57:21 is_selinux_enabled(0, 0xb7e493a5, 255, 4096, 0) = 0 15:57:21 setlocale(6, "") = "zh_CN.GBK" ... 在各行前面显示一个绝对时间戳。 -tt # ltrace -tt id 15:58:08.473989 __libc_start_main(0x80490f0, 1, 0xbff524b4, 0x804d190, 0x804d180 15:58:08.475537 is_selinux_enabled(0, 0xb7e3d3a5, 255, 4096, 0) = 0 15:58:08.477536 setlocale(6, "") = "zh_CN.GBK" ... 比-t多了微秒 -ttt # ltrace -ttt id 1380009528.393741 __libc_start_main(0x80490f0, 1, 0xbfab7014, 0x804d190, 0x804d180 1380009528.395270 is_selinux_enabled(0, 0xb7e343a5, 255, 4096, 0) = 0 1380009528.397227 setlocale(6, "") = "zh_CN.GBK" ... 前面的1380009528是自epoch以来的秒数,后面仍是微秒。 $ perl -le 'print scalar localtime 1380009528' Tue Sep 24 15:58:48 2013 -T # ltrace -T id __libc_start_main(0x80490f0, 1, 0xbfbaa104, 0x804d190, 0x804d180 is_selinux_enabled(0, 0xb7e3d3a5, 255, 4096, 0) = 0 <0.000164> setlocale(6, "") = "zh_CN.GBK" <0.000316> ... 在各行尾部显示本次库函数调用耗费的时间 -u username 以指定username执行程序并跟踪之。 -X extern Some architectures need to know where to set a breakpoint that will be hit after the dynamic linker has run. If this flag is used, then the breakpoint is set at extern, which must be an external function. By default, '_start' is used. NOTE: this flag is only available on the architectures that need it. -x extern Trace the external function extern. This option may be repeated. 不清楚什么时候会用到-X、-x这两个参数。 -V, --version 显示版本信息 # ltrace -V ltrace version 0.5.3. BUGS -f有时跟踪子进程不灵 只能工作在Linux上 只支持ELF32 无法跟踪通过dlopen()加载的库文件 FILES /etc/ltrace.conf 系统级配置文件 ~/.ltrace.conf 用户级配置文件,覆盖/etc/ltrace.conf SEE ALSO strace(1), ptrace(2) -------------------------------------------------------------------------- 2.27 GDB中一些有用的info命令 A: # gdb id GNU gdb (GDB) 7.0.1-debian (gdb) info files Symbols from "/usr/bin/id". Local exec file: `/usr/bin/id', file type elf32-i386. Entry point: 0x8048e60 0x08048114 - 0x08048127 is .interp 0x08048128 - 0x08048148 is .note.ABI-tag 0x08048148 - 0x080482c4 is .hash 0x080482c4 - 0x0804830c is .gnu.hash 0x0804830c - 0x0804868c is .dynsym 0x0804868c - 0x080488e7 is .dynstr 0x080488e8 - 0x08048958 is .gnu.version 0x08048958 - 0x080489c8 is .gnu.version_r 0x080489c8 - 0x080489e8 is .rel.dyn 0x080489e8 - 0x08048b50 is .rel.plt 0x08048b50 - 0x08048b80 is .init 0x08048b80 - 0x08048e60 is .plt 0x08048e60 - 0x0804d25c is .text 0x0804d25c - 0x0804d278 is .fini 0x0804d280 - 0x0804e11c is .rodata 0x0804e11c - 0x0804e120 is .eh_frame 0x0804f120 - 0x0804f128 is .ctors 0x0804f128 - 0x0804f130 is .dtors 0x0804f130 - 0x0804f134 is .jcr 0x0804f134 - 0x0804f20c is .dynamic 0x0804f20c - 0x0804f214 is .got 0x0804f214 - 0x0804f2d4 is .got.plt 0x0804f2d4 - 0x0804f2f8 is .data 0x0804f300 - 0x0804f4a4 is .bss (gdb) b *0x8048e60 Breakpoint 1 at 0x8048e60 (gdb) r Starting program: /usr/bin/id Breakpoint 1, 0x08048e60 in ?? () (gdb) info auxv 32 AT_SYSINFO Special system info/entry points 0xb7fe4400 33 AT_SYSINFO_EHDR System-supplied DSO's ELF header 0xffffe000 16 AT_HWCAP Machine-dependent CPU capability hints 0xfebfbff 6 AT_PAGESZ System page size 4096 17 AT_CLKTCK Frequency of times() 100 3 AT_PHDR Program headers for program 0x8048034 4 AT_PHENT Size of program header entry 32 5 AT_PHNUM Number of program headers 7 7 AT_BASE Base address of interpreter 0xb7fe5000 8 AT_FLAGS Flags 0x0 9 AT_ENTRY Entry point of program 0x8048e60 11 AT_UID Real user ID 0 12 AT_EUID Effective user ID 0 13 AT_GID Real group ID 0 14 AT_EGID Effective group ID 0 23 AT_SECURE Boolean, was exec setuid-like? 0 15 AT_PLATFORM String identifying platform 0xbffffe3b "i686" 0 AT_NULL End of vector 0x0 (gdb) info proc stat process 4736 ... Start of text: 0x8048000 End of text: 0x804e120 Start of stack: 0xbffffd40 (gdb) info proc mappings process 4736 cmdline = '/usr/bin/id' cwd = '/tmp' exe = '/usr/bin/id' Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x804f000 0x7000 0 /usr/bin/id 0x804f000 0x8050000 0x1000 0x6000 /usr/bin/id 0x8050000 0x8071000 0x21000 0x8050000 [heap] 0xb7e6c000 0xb7e6d000 0x1000 0xb7e6c000 0xb7e6d000 0xb7e6f000 0x2000 0 /lib/i686/cmov/libdl-2.11.2.so 0xb7e6f000 0xb7e70000 0x1000 0x1000 /lib/i686/cmov/libdl-2.11.2.so 0xb7e70000 0xb7e71000 0x1000 0x2000 /lib/i686/cmov/libdl-2.11.2.so 0xb7e71000 0xb7e72000 0x1000 0xb7e71000 0xb7e72000 0xb7fb2000 0x140000 0 /lib/i686/cmov/libc-2.11.2.so 0xb7fb2000 0xb7fb4000 0x2000 0x13f000 /lib/i686/cmov/libc-2.11.2.so 0xb7fb4000 0xb7fb5000 0x1000 0x141000 /lib/i686/cmov/libc-2.11.2.so 0xb7fb5000 0xb7fb8000 0x3000 0xb7fb5000 0xb7fb8000 0xb7fcf000 0x17000 0 /lib/libselinux.so.1 0xb7fcf000 0xb7fd1000 0x2000 0x16000 /lib/libselinux.so.1 0xb7fe2000 0xb7fe4000 0x2000 0xb7fe2000 0xb7fe4000 0xb7fe5000 0x1000 0xb7fe4000 [vdso] 0xb7fe5000 0xb8000000 0x1b000 0 /lib/ld-2.11.2.so 0xb8000000 0xb8001000 0x1000 0x1a000 /lib/ld-2.11.2.so 0xb8001000 0xb8002000 0x1000 0x1b000 /lib/ld-2.11.2.so 0xbffeb000 0xc0000000 0x15000 0xbffeb000 [stack] (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fe5830 0xb7ffb88f Yes (*) /lib/ld-linux.so.2 0xb7fbc2b0 0xb7fcba38 Yes (*) /lib/libselinux.so.1 0xb7e88a50 0xb7f7e00c Yes (*) /lib/i686/cmov/libc.so.6 0xb7e6da40 0xb7e6e988 Yes (*) /lib/i686/cmov/libdl.so.2 (*): Shared library is missing debugging information. (gdb) info sharedlibrary libc From To Syms Read Shared Object Library 0xb7e88a50 0xb7f7e00c Yes (*) /lib/i686/cmov/libc.so.6 (*): Shared library is missing debugging information. (gdb) info functions All defined functions: Non-debugging symbols: 0x08048b50 _init 0x08048b90 abort 0x08048b90 abort@plt 0x08048ba0 __errno_location 0x08048ba0 __errno_location@plt 0x08048bb0 __cxa_atexit 0x08048bb0 __cxa_atexit@plt 0x08048bc0 sysconf 0x08048bc0 sysconf@plt ... 0xb7e6db00 dlopen 0xb7e6dcb0 dlclose 0xb7e6dcf0 dlsym 0xb7e6ddd0 dlvsym 0xb7e6e160 dlerror 0xb7e6e3d0 dladdr 0xb7e6e420 dladdr1 0xb7e6e4b0 dlinfo 0xb7e6e770 dlmopen 0xb7e6e8e0 dlopen (gdb) info functions ^mem All functions matching regular expression "^mem": Non-debugging symbols: 0x08048c40 memset 0x08048c40 memset@plt 0x08048d00 memcpy 0x08048d00 memcpy@plt 0xb7fbbd48 memset 0xb7fbbd48 memset@plt 0xb7fbbd78 mempcpy 0xb7fbbd78 mempcpy@plt 0xb7fbbf48 memcpy 0xb7fbbf48 memcpy@plt 0xb7e88a1c memalign 0xb7e88a1c memalign@plt 0xb7ee20d0 memalign 0xb7ee60d0 memchr 0xb7ee6270 memcmp 0xb7ee6560 memmove 0xb7ee65d0 memset 0xb7ee6630 mempcpy 0xb7ee6a80 memccpy 0xb7ee6ad0 memcpy 0xb7ee7340 memfrob 0xb7ee7a00 memmem 0xb7eeb290 memrchr (gdb) info variables All defined variables: Non-debugging symbols: 0x0804d284 _IO_stdin_used 0x0804f300 optind 0x0804f304 stderr 0x0804f320 stdout 0xb8000ca0 _dl_argv 0xb8000cc0 _rtld_global_ro 0xb8000f1c __libc_enable_secure 0xb8000f20 __libc_stack_end 0xb8001020 _rtld_global 0xb80018e0 _r_debug 0xb7fd01fc obj_class_compat 0xb7fd0204 myprintf ... 0xb7fb7928 __key_decryptsession_pk_LOCAL 0xb7fb792c svcauthdes_stats 0xb7e7004c _dlfcn_hook (gdb) info variables program.*name All variables matching regular expression "program.*name": Non-debugging symbols: 0xb7fb4344 program_invocation_name 0xb7fb4348 program_invocation_short_name (gdb) x/wx 0xb7fb4344 0xb7fb4344 : 0xbffffe4a (gdb) x/s 0xbffffe4a (gdb) x/s program_invocation_name (gdb) x/s *0xb7fb4344 (gdb) x/s {unsigned int}0xb7fb4344 0xbffffe4a: "/usr/bin/id" (gdb) c Continuing. uid=0(root) gid=0(root) groups=0(root) Program exited normally. (gdb) q 2.28 GDB中如何清屏 Q: 在cdb中可以执行.cls清屏,gdb中如何清屏? A: 试试Ctrl-L,或者执行"shell clear" 2.29 在GDB中如何demangle C++符号 Q: -------------------------------------------------------------------------- /* * g++-4.4 -Wall -pipe -O3 -s -o hello hello.cpp * g++-4.4 -Wall -pipe -O3 -o hello_not_stripped hello.cpp */ #include using namespace std; int main ( int argc, char * argv[] ) { cout << "Hello World\r\n"; return( 0 ); } /* end of main */ -------------------------------------------------------------------------- # g++-4.4 -Wall -pipe -O3 -s -o hello hello.cpp # file -b hello ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped # g++-4.4 -Wall -pipe -O3 -o hello_not_stripped hello.cpp # file -b hello_not_stripped ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped # readelf -h hello | grep "Entry point" Entry point address: 0x8048590 # gdb ./hello GNU gdb (GDB) 7.0.1-debian (gdb) b *0x8048590 Breakpoint 1 at 0x8048590 (gdb) r Starting program: /tmp/hello Breakpoint 1, 0x08048590 in ?? () (gdb) x/15i 0x8048590 0x8048590: xor %ebp,%ebp 0x8048592: pop %esi 0x8048593: mov %esp,%ecx 0x8048595: and $0xfffffff0,%esp 0x8048598: push %eax 0x8048599: push %esp 0x804859a: push %edx 0x804859b: push $0x80486b0 0x80485a0: push $0x80486c0 0x80485a5: push %ecx 0x80485a6: push %esi 0x80485a7: push $0x8048680 // 这个就是main() 0x80485ac: call 0x8048548 <__libc_start_main@plt> 0x80485b1: hlt 0x80485b2: nop (gdb) x/12i 0x8048680 0x8048680: push %ebp 0x8048681: mov %esp,%ebp 0x8048683: and $0xfffffff0,%esp 0x8048686: sub $0x10,%esp 0x8048689: movl $0xd,0x8(%esp) 0x8048691: movl $0x8048770,0x4(%esp) 0x8048699: movl $0x8049940,(%esp) 0x80486a0: call 0x8048578 <_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i@plt> 0x80486a5: xor %eax,%eax 0x80486a7: leave 0x80486a8: ret 0x80486a9: nop 0x80486a0处的反汇编代码太难看了,我想看到orignal non-mangling函数名。 A: scz 2013-09-29 11:38 有三处设置会影响demangle,下面是它们的缺省设置: (gdb) show print asm-demangle Demangling of C++/ObjC names in disassembly listings is off. (gdb) show print demangle Demangling of encoded C++/ObjC names when displaying symbols is on. (gdb) show demangle-style The current C++ demangling style is "auto". "set print asm-demangle on/off"会影响x/i、disassemble等命令的输出。 "set print demangle on/off"会影响"info function"、"info variables"等命令的 输出。 "set demangle-style"会显示可用参数列表: none auto gnu lucid arm hp edg gnu-v3 java gnat 对我们来说auto足够了。 为了方便调试,可以始终执行: set print asm-demangle on set print demangle on set demangle-style auto 但是,即使这样干了,0x80486a0处的反汇编代码仍无变化,这是一个BUG。当符号以 @plt结尾时,由于@plt被一起拿去demangle,致使demangle失败。直至GDB 7.4,该 BUG仍然存在: https://sourceware.org/bugzilla/show_bug.cgi?id=12021 如果自动demangle失败,可以手工demangle: (gdb) help set lang Set the current source language. The currently understood settings are: local or auto Automatic setting based on source file ada Use the Ada language c Use the C language c++ Use the C++ language asm Use the Asm language minimal Use the Minimal language fortran Use the Fortran language objective-c Use the Objective-c language java Use the Java language modula-2 Use the Modula-2 language pascal Use the Pascal language scheme Use the Scheme language (gdb) show lang The current source language is "auto; currently c". (gdb) set lang c++ (gdb) maintenance demangle _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i std::basic_ostream >& std::__ostream_insert >(std::basic_ostream >&, char const*, int) 一般可以执行: set lang c++ main demangle ... 在GDB之外有命令行工具c++filt可用: $ c++filt _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i std::basic_ostream >& std::__ostream_insert >(std::basic_ostream >&, char const*, int) $ c++filt -p -s gnu-v3 _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i std::__ostream_insert > -p 不显示函数参数的类型 -s 可用参数列表: auto gnu lucid arm hp edg gnu-v3 java gnat D: scz 下面是另一个目标程序 (gdb) set disassembly-flavor intel (gdb) set print asm-demangle off (gdb) set print demangle off (gdb) set demangle-style auto (gdb) disas main Dump of assembler code for function main: 0x0804b8c4 : push ebp ... 0x0804b8f1 : call 0x80523f8 <_ZN9CStatBase10InitializeEv> 0x0804b8f6 : add esp,0x10 0x0804b8f9 : sub esp,0xc 0x0804b8fc : push 0x8067728 0x0804b901 : call 0x8051404 <_ZN9CServerIP10InitializeEv> 0x0804b906 : add esp,0x10 0x0804b909 : sub esp,0xc 0x0804b90c : push 0x80675a0 0x0804b911 : call 0x804c586 <_ZN8CManager15StartNetProcessEv> 0x0804b916 : add esp,0x10 0x0804b919 : mov eax,0x0 0x0804b91e : leave 0x0804b91f : ret End of assembler dump. (gdb) info variables All defined variables: Non-debugging symbols: 0x0805f348 _fp_hw 0x0805f34c _IO_stdin_used 0x0805f37c _ZTSSt12out_of_range 0x0805f4a3 _ZTS13CThreadAttack 0x0805f4b3 _ZTS17CThreadHostStatus 0x0805f4c7 _ZTS18CThreadTaskManager 0x0805f4dc _ZTS12CThreadTimer 0x0805f571 _ZTS5CMd5A 0x0805f578 _ZTS7CThread ... (gdb) info functions All defined functions: Non-debugging symbols: 0x08049d30 _init 0x08049d58 pthread_cond_wait 0x08049d58 pthread_cond_wait@plt 0x08049d68 pthread_cond_timedwait 0x08049d68 pthread_cond_timedwait@plt 0x08049d78 readlink 0x08049d78 readlink@plt 0x08049d88 pthread_attr_init 0x08049d88 pthread_attr_init@plt 0x08049d98 usleep 0x08049d98 usleep@plt 0x08049da8 __cxa_allocate_exception 0x08049da8 __cxa_allocate_exception@plt 0x08049db8 _ZNSsaSEPKc 0x08049db8 _ZNSsaSEPKc@plt 0x08049dc8 rename 0x08049dc8 rename@plt 0x08049dd8 _ZNKSs7compareERKSs 0x08049dd8 _ZNKSs7compareERKSs@plt 0x08049de8 _ZNSt14basic_ofstreamIcSt11char_traitsIcEEC1Ev 0x08049de8 _ZNSt14basic_ofstreamIcSt11char_traitsIcEEC1Ev@plt 0x08049df8 _ZNSsC1EPKcjRKSaIcE 0x08049df8 _ZNSsC1EPKcjRKSaIcE@plt 0x08049e08 _ZNSaIcEC1Ev 0x08049e08 _ZNSaIcEC1Ev@plt ... (gdb) set print asm-demangle on (gdb) set print demangle on (gdb) disas main Dump of assembler code for function main: 0x0804b8c4 : push ebp ... 0x0804b8f1 : call 0x80523f8 0x0804b8f6 : add esp,0x10 0x0804b8f9 : sub esp,0xc 0x0804b8fc : push 0x8067728 0x0804b901 : call 0x8051404 0x0804b906 : add esp,0x10 0x0804b909 : sub esp,0xc 0x0804b90c : push 0x80675a0 0x0804b911 : call 0x804c586 0x0804b916 : add esp,0x10 0x0804b919 : mov eax,0x0 0x0804b91e : leave 0x0804b91f : ret End of assembler dump. (gdb) info variables All defined variables: Non-debugging symbols: 0x0805f348 _fp_hw 0x0805f34c _IO_stdin_used 0x0805f37c typeinfo name for std::out_of_range 0x0805f4a3 typeinfo name for CThreadAttack 0x0805f4b3 typeinfo name for CThreadHostStatus 0x0805f4c7 typeinfo name for CThreadTaskManager 0x0805f4dc typeinfo name for CThreadTimer 0x0805f571 typeinfo name for CMd5A 0x0805f578 typeinfo name for CThread ... (gdb) info functions All defined functions: Non-debugging symbols: 0x08049d30 _init 0x08049d58 pthread_cond_wait 0x08049d58 pthread_cond_wait@plt 0x08049d68 pthread_cond_timedwait 0x08049d68 pthread_cond_timedwait@plt 0x08049d78 readlink 0x08049d78 readlink@plt 0x08049d88 pthread_attr_init 0x08049d88 pthread_attr_init@plt 0x08049d98 usleep 0x08049d98 usleep@plt 0x08049da8 __cxa_allocate_exception 0x08049da8 __cxa_allocate_exception@plt 0x08049db8 std::string::operator=(char const*) 0x08049db8 _ZNSsaSEPKc@plt 0x08049dc8 rename 0x08049dc8 rename@plt 0x08049dd8 std::string::compare(std::string const&) const 0x08049dd8 _ZNKSs7compareERKSs@plt 0x08049de8 std::basic_ofstream >::basic_ofstream() 0x08049de8 _ZNSt14basic_ofstreamIcSt11char_traitsIcEEC1Ev@plt 0x08049df8 std::basic_string, std::allocator >::basic_string(char const*, unsigned int, std::allocator const&) 0x08049df8 _ZNSsC1EPKcjRKSaIcE@plt 0x08049e08 std::allocator::allocator() 0x08049e08 _ZNSaIcEC1Ev@plt ... (gdb) 注意到那些带有@plt后缀的符号demangle失败。 2.30 GDB中如何禁止"Type to continue, or q to quit" Q: 已经使用了"set logging on",不想看到"Type to continue, or q to quit" A: 有两种方案: set height 0 set pagination off 2.31 GDB中如何调用指定动态链接库中的导出函数 Q: -------------------------------------------------------------------------- /* * gcc -fPIC -Wall -pipe -O3 -c -o helper.o helper.c * ld -s -m elf_i386 -shared -soname helper.so -o helper.so helper.o */ #include void hexdump ( FILE *out, unsigned char *in, unsigned int insize, unsigned int count ) { unsigned int offset, k, j, i, m; if ( insize <= 0 || count <= 0 || NULL == in || NULL == out ) { return; } #if 0 fprintf( out, "[ %u bytes ] -> %u bytes per line\n", insize, count ); #endif i = 0; offset = 0; m = ( count + 1 ) / 2; for ( k = insize / count; k > 0; k--, offset += count ) { fprintf( out, "%08X ", ( unsigned int )in + offset ); for ( j = 0; j < count; j++, i++ ) { if ( m == j ) { fprintf( out, "-%02X", in[i] ); } else { fprintf( out, " %02X", in[i] ); } } fprintf( out, " " ); i -= count; for ( j = 0; j < count; j++, i++ ) { /* * if ( isprint( ( int )in[i] ) ) */ #if 1 if ( ( in[i] >= ' ' ) && ( in[i] != 0x7F ) && ( in[i] < 0xFF ) ) #else if ( ( in[i] >= ' ' ) && ( in[i] < 0x7F ) ) #endif { fprintf( out, "%c", in[i] ); } else { fprintf( out, "." ); } } fprintf( out, "\n" ); } /* end of for */ k = insize - i; if ( k <= 0 ) { return; } fprintf( out, "%08X ", ( unsigned int )in + offset ); for ( j = 0 ; j < k; j++, i++ ) { if ( m == j ) { fprintf( out, "-%02X", in[i] ); } else { fprintf( out, " %02X", in[i] ); } } i -= k; for ( j = count - k; j > 0; j-- ) { fprintf( out, " " ); } fprintf( out, " " ); for ( j = 0; j < k; j++, i++ ) { #if 1 if ( ( in[i] >= ' ' ) && ( in[i] != 0x7F ) && ( in[i] < 0xFF ) ) #else if ( ( in[i] >= ' ' ) && ( in[i] < 0x7F ) ) #endif { fprintf( out, "%c", in[i] ); } else { fprintf( out, "." ); } } fprintf( out, "\n" ); return; } /* end of hexdump */ -------------------------------------------------------------------------- $ gcc -fPIC -Wall -pipe -O3 -c -o helper.o helper.c $ ld -s -m elf_i386 -shared -soname helper.so -o helper.so helper.o $ file -b helper.so ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped $ nm -D helper.so | grep " T " 00000200 T hexdump 用gdb调试/usr/bin/id时,我想调用helper.so中的hexdump()函数,如何做? A: scz@nsfocus 2013-09-30 13:32 # gdb /usr/bin/id GNU gdb (GDB) 7.0.1-debian (gdb) info files Symbols from "/usr/bin/id". Local exec file: `/usr/bin/id', file type elf32-i386. Entry point: 0x8048e60 ... 在_start()设临时断点 (gdb) tb *0x8048e60 Temporary breakpoint 1 at 0x8048e60 (gdb) r Starting program: /usr/bin/id Temporary breakpoint 1, 0x08048e60 in ?? () (gdb) x/15i $pc 0x8048e60: xor %ebp,%ebp 0x8048e62: pop %esi 0x8048e63: mov %esp,%ecx 0x8048e65: and $0xfffffff0,%esp 0x8048e68: push %eax 0x8048e69: push %esp 0x8048e6a: push %edx 0x8048e6b: push $0x804d180 0x8048e70: push $0x804d190 0x8048e75: push %ecx 0x8048e76: push %esi 0x8048e77: push $0x80490f0 // 这个就是main() 0x8048e7c: call 0x8048c50 <__libc_start_main@plt> 0x8048e81: hlt 0x8048e82: nop 在main()设临时断点 (gdb) tb *0x80490f0 Temporary breakpoint 2 at 0x80490f0 (gdb) c Continuing. Temporary breakpoint 2, 0x080490f0 in ?? () esp+c处的值是envp[] (gdb) x/4wx $esp 0xbffffc9c: 0xb7e88c76 0x00000001 0xbffffd44 0xbffffd4c 查看envp[] (gdb) x/20wx *(unsigned int *)($esp+0xc) 0xbffffd4c: 0xbffffe56 0xbffffe61 0xbffffe71 0xbffffe91 0xbffffd5c: 0xbffffea4 0xbffffeae 0xbffffece 0xbffffefb 0xbffffd6c: 0xbfffff07 0xbfffff1b 0xbfffff5d 0xbfffff6c 0xbffffd7c: 0xbfffff75 0xbfffff84 0xbfffff8d 0xbfffff95 0xbffffd8c: 0xbfffffa0 0xbfffffb2 0xbfffffbf 0x00000000 查看envp[1]、envp[2]、envp[3] (gdb) x/s *(unsigned int *)(*(unsigned int *)($esp+0xc)) 0xbffffe56: "TERM=vt100" (gdb) x/s *(unsigned int *)(*(unsigned int *)($esp+0xc)+4) 0xbffffe61: "SHELL=/bin/bash" (gdb) x/s *(unsigned int *)(*(unsigned int *)($esp+0xc)+8) 0xbffffe71: "SSH_CLIENT=192.168.7.2 45326 22" 加载helper.so并调用hexdump() (gdb) call (void)dlopen("/tmp/helper.so",1) (gdb) call (void)hexdump(stdout,*(unsigned int *)(*(unsigned int *)($esp+0xc)),256,16) BFFFFE56 54 45 52 4D 3D 76 74 31-30 30 00 53 48 45 4C 4C TERM=vt100.SHELL BFFFFE66 3D 2F 62 69 6E 2F 62 61-73 68 00 53 53 48 5F 43 =/bin/bash.SSH_C BFFFFE76 4C 49 45 4E 54 3D 31 39-32 2E 31 36 38 2E 37 2E LIENT=192.168.7. BFFFFE86 32 20 34 35 33 32 36 20-32 32 00 53 53 48 5F 54 2 45326 22.SSH_T BFFFFE96 54 59 3D 2F 64 65 76 2F-70 74 73 2F 32 00 55 53 TY=/dev/pts/2.US BFFFFEA6 45 52 3D 72 6F 6F 74 00-4C 44 5F 4C 49 42 52 41 ER=root.LD_LIBRA BFFFFEB6 52 59 5F 50 41 54 48 3D-2F 75 73 72 2F 6C 6F 63 RY_PATH=/usr/loc BFFFFEC6 61 6C 2F 6C 69 62 3A 00-53 53 48 5F 41 55 54 48 al/lib:.SSH_AUTH BFFFFED6 5F 53 4F 43 4B 3D 2F 74-6D 70 2F 73 73 68 2D 48 _SOCK=/tmp/ssh-H BFFFFEE6 70 55 79 6D 61 34 33 30-32 2F 61 67 65 6E 74 2E pUyma4302/agent. BFFFFEF6 34 33 30 32 00 43 4F 4C-55 4D 4E 53 3D 31 33 32 4302.COLUMNS=132 BFFFFF06 00 4D 41 49 4C 3D 2F 76-61 72 2F 6D 61 69 6C 2F .MAIL=/var/mail/ BFFFFF16 72 6F 6F 74 00 50 41 54-48 3D 2F 75 73 72 2F 6C root.PATH=/usr/l BFFFFF26 6F 63 61 6C 2F 73 62 69-6E 3A 2F 75 73 72 2F 6C ocal/sbin:/usr/l BFFFFF36 6F 63 61 6C 2F 62 69 6E-3A 2F 75 73 72 2F 73 62 ocal/bin:/usr/sb BFFFFF46 69 6E 3A 2F 75 73 72 2F-62 69 6E 3A 2F 73 62 69 in:/usr/bin:/sbi (gdb) dlopen()第2形参等于1,即RTLD_LAZY。说穿了很简单,在gdb中调用dlopen()加载指 定动态链接库,然后在gdb中调用该库的导出函数。 (gdb) info functions ^dlopen All functions matching regular expression "^dlopen": Non-debugging symbols: 0xb7fbbfd8 dlopen 0xb7fbbfd8 dlopen@plt 0xb7e6db00 dlopen 0xb7e6e8e0 dlopen (gdb) info functions ^hexdump All functions matching regular expression "^hexdump": Non-debugging symbols: 0xb7fe0200 hexdump (gdb) disas hexdump Dump of assembler code for function hexdump: 0xb7fe0200 : push %ebp 0xb7fe0201 : mov %esp,%ebp ... 确认helper.so已经出现在进程空间中 (gdb) info proc mappings process 31129 cmdline = '/usr/bin/id' cwd = '/tmp' exe = '/usr/bin/id' Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x804f000 0x7000 0 /usr/bin/id 0x804f000 0x8050000 0x1000 0x6000 /usr/bin/id 0x8050000 0x8071000 0x21000 0x8050000 [heap] 0xb7e6c000 0xb7e6d000 0x1000 0xb7e6c000 0xb7e6d000 0xb7e6f000 0x2000 0 /lib/i686/cmov/libdl-2.11.2.so 0xb7e6f000 0xb7e70000 0x1000 0x1000 /lib/i686/cmov/libdl-2.11.2.so 0xb7e70000 0xb7e71000 0x1000 0x2000 /lib/i686/cmov/libdl-2.11.2.so 0xb7e71000 0xb7e72000 0x1000 0xb7e71000 0xb7e72000 0xb7fb2000 0x140000 0 /lib/i686/cmov/libc-2.11.2.so 0xb7fb2000 0xb7fb4000 0x2000 0x13f000 /lib/i686/cmov/libc-2.11.2.so 0xb7fb4000 0xb7fb5000 0x1000 0x141000 /lib/i686/cmov/libc-2.11.2.so 0xb7fb5000 0xb7fb8000 0x3000 0xb7fb5000 0xb7fb8000 0xb7fcf000 0x17000 0 /lib/libselinux.so.1 0xb7fcf000 0xb7fd1000 0x2000 0x16000 /lib/libselinux.so.1 0xb7fdf000 0xb7fe0000 0x1000 0xb7fdf000 0xb7fe0000 0xb7fe1000 0x1000 0 /tmp/helper.so 0xb7fe1000 0xb7fe2000 0x1000 0 /tmp/helper.so 0xb7fe2000 0xb7fe4000 0x2000 0xb7fe2000 0xb7fe4000 0xb7fe5000 0x1000 0xb7fe4000 [vdso] 0xb7fe5000 0xb8000000 0x1b000 0 /lib/ld-2.11.2.so 0xb8000000 0xb8001000 0x1000 0x1a000 /lib/ld-2.11.2.so 0xb8001000 0xb8002000 0x1000 0x1b000 /lib/ld-2.11.2.so 0xbffeb000 0xc0000000 0x15000 0xbffeb000 [stack] (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fe5830 0xb7ffb88f Yes (*) /lib/ld-linux.so.2 0xb7fbc2b0 0xb7fcba38 Yes (*) /lib/libselinux.so.1 0xb7e88a50 0xb7f7e00c Yes (*) /lib/i686/cmov/libc.so.6 0xb7e6da40 0xb7e6e988 Yes (*) /lib/i686/cmov/libdl.so.2 0xb7fe0200 0xb7fe0549 Yes (*) /tmp/helper.so (*): Shared library is missing debugging information. (gdb) 用call调用dlopen()不是最佳选择,应该用p/x调用dlopen(),这样可以记录返回值, 后面可以用该返回值调用dlsym()、dlclose()。 再次加载helper.so,只是为了获取返回值 (gdb) p/x dlopen("/tmp/helper.so",1) $1 = 0x80506e8 调用dlsym()、dlclose(),由于dlopen()过2次,因此需要dlclose()2次,第3次dlclose() 返回-1表示失败 (gdb) p/x dlsym(0x80506e8,"hexdump") $2 = 0xb7fe0200 (gdb) p/x dlclose(0x80506e8) $3 = 0x0 (gdb) p/x dlclose(0x80506e8) $4 = 0x0 (gdb) p/x dlclose(0x80506e8) $5 = 0xffffffff 确认helper.so已经被移出进程空间,hexdump这个符号已经无法解析 (gdb) info sharedlibrary helper No shared libraries matched. (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fe5830 0xb7ffb88f Yes (*) /lib/ld-linux.so.2 0xb7fbc2b0 0xb7fcba38 Yes (*) /lib/libselinux.so.1 0xb7e88a50 0xb7f7e00c Yes (*) /lib/i686/cmov/libc.so.6 0xb7e6da40 0xb7e6e988 Yes (*) /lib/i686/cmov/libdl.so.2 (*): Shared library is missing debugging information. (gdb) info functions ^hexdump All functions matching regular expression "^hexdump": (gdb) 参看: Loading code into an active GDB session https://sourceware.org/gdb/wiki/LoadingCodeIntoActiveSession FAQ https://sourceware.org/gdb/wiki/FAQ 过去有一种很蠢的办法: (gdb) set $dlopen={void * (*) ( char *, int )}dlopen (gdb) set $dlsym={void * (*) ( void *, char * )}dlsym (gdb) set $dlclose={int (*) ( void * )}dlclose (gdb) set $helper=$dlopen("/tmp/helper.so",1) (gdb) set $hexdump=$dlsym($helper,"hexdump") (gdb) call (void)$hexdump(stdout,*(unsigned int *)(*(unsigned int *)($esp+0xc)),32,16) BFFFFE56 54 45 52 4D 3D 76 74 31-30 30 00 53 48 45 4C 4C TERM=vt100.SHELL BFFFFE66 3D 2F 62 69 6E 2F 62 61-73 68 00 53 53 48 5F 43 =/bin/bash.SSH_C (gdb) info sharedlibrary helper From To Syms Read Shared Object Library 0xb7fe0200 0xb7fe0549 Yes (*) /tmp/helper.so (*): Shared library is missing debugging information. (gdb) p/x $dlclose($helper) $1 = 0x0 (gdb) info sharedlibrary helper No shared libraries matched. (gdb) 至少对于GDB 7.x,不再需要这种蠢办法。 不管是过去还是现在,如果当前被调试进程空间中没有dlopen(),就无法使用本节介 绍的技术,在gdb中用"info function dlopen"确认,在gdb外用ldd确认: # ldd `which id` linux-gate.so.1 => (0xffffe000) libselinux.so.1 => /lib/libselinux.so.1 (0xb7fd3000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7e8d000) libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7e88000) /lib/ld-linux.so.2 (0x80000000) /bin/uname没有使用libdl.so,gdb调试它时就无法用dlopen()加载指定动态链接库: # ldd `which uname` linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7ea6000) /lib/ld-linux.so.2 (0x80000000) # gdb /bin/uname GNU gdb (GDB) 7.0.1-debian (gdb) info files Symbols from "/bin/uname". Local exec file: `/bin/uname', file type elf32-i386. Entry point: 0x8048b00 ... (gdb) tb *0x8048b00 Temporary breakpoint 1 at 0x8048b00 (gdb) r Starting program: /bin/uname Temporary breakpoint 1, 0x08048b00 in ?? () (gdb) p/x dlopen("/tmp/helper.so",1) No symbol table is loaded. Use the "file" command. Windows平台LoadLibrary()位于kernel32.dll中,几乎每个进程都会加载kernel32.dll, 因此在windbg中总是可以加载指定动态链接库,比起Linux平台限制要少。 在有dlopen()可用的情况下,前述技术实际可以用来给GDB写插件,完成很多复杂的 增强性操作。另外有个限制,如果用GDB分析coredump,此时并没有真正调试一个进 程,无法调用dlopen(): (gdb) call (void)dlopen("/root/src/helper.so",1) You can't do that without a process to debug. 2.32 GDB调试多线程进程时如何只调试指定线程同时挂起其他线程 A: scz@nsfocus 2013-10-24 14:55 简单点说: info threads // 查看多线程 thread // 切换当前线程 set scheduler-locking on // 锁定在当前线程中 -------------------------------------------------------------------------- /* * gcc-4.4 -D_GNU_SOURCE -Wall -pipe -O0 -g -o scheduler_locking_test scheduler_locking_test.c -pthread */ #include #include #include #include static __thread unsigned int loopcount = 0xFFFFFFFF; static __thread unsigned int something; static void * thread_func ( void *arg ) { unsigned int i; unsigned int j = ( unsigned int )arg; for ( i = 0; i < loopcount; i++ ) { something = i + j; if ( 0 == i % 0x1000000 ) { printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); } } return( NULL ); } /* end of thread_func */ int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; unsigned int thread_num, i; pthread_t *tid = NULL; if ( argc < 2 ) { fprintf( stderr, "Usage: %s \n", argv[0] ); goto main_exit; } thread_num = ( unsigned int )strtoul( argv[1], NULL, 0 ); if ( NULL == ( tid = ( pthread_t * )calloc( thread_num, sizeof( pthread_t ) ) ) ) { perror( "calloc() failed" ); goto main_exit; } for ( i = 0; i < thread_num; i++ ) { pthread_create( &tid[i], NULL, &thread_func, ( void * )i ); } for ( i = 0; i < thread_num; i++ ) { pthread_join( tid[i], NULL ); } ret = EXIT_SUCCESS; main_exit: if ( NULL != tid ) { free( tid ); tid = NULL; } return( ret ); } /* end of main */ -------------------------------------------------------------------------- $ gdb -q ./scheduler_locking_test Reading symbols from /tmp/scheduler_locking_test...done. (gdb) tb 22 Temporary breakpoint 1 at 0x8048665: file scheduler_locking_test.c, line 22. (gdb) list 22 17 for ( i = 0; i < loopcount; i++ ) 18 { 19 something = i + j; 20 if ( 0 == i % 0x1000000 ) 21 { 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); 23 } 24 } 25 return( NULL ); 26 } /* end of thread_func */ (gdb) r 3 Starting program: /tmp/scheduler_locking_test 3 [Thread debugging using libthread_db enabled] [New Thread 0xb7e6fb70 (LWP 840)] [Switching to Thread 0xb7e6fb70 (LWP 840)] Temporary breakpoint 1, thread_func (arg=0x0) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) info threads [New Thread 0xb766eb70 (LWP 841)] 3 Thread 0xb766eb70 (LWP 841) 0xb7f3de68 in clone () from /lib/i686/cmov/libc.so.6 * 2 Thread 0xb7e6fb70 (LWP 840) thread_func (arg=0x0) at scheduler_locking_test.c:22 1 Thread 0xb7e706d0 (LWP 837) 0xb7f3de68 in clone () from /lib/i686/cmov/libc.so.6 1号线程是主线程,2、3号线程是新创建的子线程,前面的星号表明2号线程是当前线 程。 (gdb) s [0] something of thread(0xB7E6FB70) is 0x00000000 [1] something of thread(0xB766EB70) is 0x00000001 17 for ( i = 0; i < loopcount; i++ ) (gdb) info threads [New Thread 0xb6e6db70 (LWP 842)] 4 Thread 0xb6e6db70 (LWP 842) 0xb7f3de68 in clone () from /lib/i686/cmov/libc.so.6 3 Thread 0xb766eb70 (LWP 841) thread_func (arg=0x1) at scheduler_locking_test.c:17 * 2 Thread 0xb7e6fb70 (LWP 840) thread_func (arg=0x0) at scheduler_locking_test.c:17 1 Thread 0xb7e706d0 (LWP 837) 0xb7fbc9f0 in __nptl_create_event () from /lib/i686/cmov/libpthread.so.0 执行step,发现3号线程也被执行了(输出了[1] ...)。很可能此时的本意是在2号线 程中step,其他线程保持挂起状态。 (gdb) help set scheduler-locking Set mode for locking scheduler during execution. off == no locking (threads may preempt at any time) on == full locking (no thread except the current thread may run) step == scheduler locked during every single-step operation. In this mode, no other thread may run during a step command. Other threads may run while stepping over a function call ('next'). (gdb) show scheduler-locking Mode for locking scheduler during execution is "off". scheduler-locking有三种值: -------------------------------------------------------------------------- off 不锁定在某一线程中,所有线程正常执行,这是缺省值 on 锁定在当前线程中,只有当前线程被执行,其他线程被挂起 step 单步过函数时其他线程正常执行,其他单步操作时锁定在当前线程中 -------------------------------------------------------------------------- (gdb) set scheduler-locking on (gdb) tb 22 Temporary breakpoint 2 at 0x8048665: file scheduler_locking_test.c, line 22. (gdb) c Continuing. Temporary breakpoint 2, thread_func (arg=0x0) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) info threads 4 Thread 0xb6e6db70 (LWP 842) 0xb7f3de68 in clone () from /lib/i686/cmov/libc.so.6 3 Thread 0xb766eb70 (LWP 841) thread_func (arg=0x1) at scheduler_locking_test.c:17 * 2 Thread 0xb7e6fb70 (LWP 840) thread_func (arg=0x0) at scheduler_locking_test.c:22 1 Thread 0xb7e706d0 (LWP 837) 0xb7fbc9f0 in __nptl_create_event () from /lib/i686/cmov/libpthread.so.0 我们锁定在2号线程中,在22行设临时断点,c之后只有2号线程resume,断下来时查 看各线程,确认只有2号线程被执行,其他线程仍留在原地。 (gdb) b 22 Breakpoint 3 at 0x8048665: file scheduler_locking_test.c, line 22. (gdb) step [0] something of thread(0xB7E6FB70) is 0x01000000 17 for ( i = 0; i < loopcount; i++ ) (gdb) c Continuing. Breakpoint 3, thread_func (arg=0x0) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) step [0] something of thread(0xB7E6FB70) is 0x02000000 17 for ( i = 0; i < loopcount; i++ ) (gdb) c Continuing. Breakpoint 3, thread_func (arg=0x0) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) next [0] something of thread(0xB7E6FB70) is 0x03000000 17 for ( i = 0; i < loopcount; i++ ) scheduler-locking为on时,死活锁定在当前线程中,不论step、next还是continue。 (gdb) set scheduler-locking step (gdb) c Continuing. [Switching to Thread 0xb6e6db70 (LWP 842)] Breakpoint 3, thread_func (arg=0x2) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) info threads * 4 Thread 0xb6e6db70 (LWP 842) thread_func (arg=0x2) at scheduler_locking_test.c:22 3 Thread 0xb766eb70 (LWP 841) thread_func (arg=0x1) at scheduler_locking_test.c:19 2 Thread 0xb7e6fb70 (LWP 840) thread_func (arg=0x0) at scheduler_locking_test.c:19 1 Thread 0xb7e706d0 (LWP 837) 0xb7fe4410 in ?? () 但scheduler-locking为step时,continue肯定不会锁定在当前线程中,前面的显示 表明当前线程已经切换成4号线程。单步过函数时其他线程正常执行,意味着此时当 前线程有可能发生切换。 (gdb) c Continuing. [2] something of thread(0xB6E6DB70) is 0x0C000002 [Switching to Thread 0xb7e6fb70 (LWP 840)] Breakpoint 3, thread_func (arg=0x0) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) step [0] something of thread(0xB7E6FB70) is 0x0F000000 [Switching to Thread 0xb6e6db70 (LWP 842)] Breakpoint 3, thread_func (arg=0x2) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); (gdb) next [2] something of thread(0xB6E6DB70) is 0x0D000002 [Switching to Thread 0xb766eb70 (LWP 841)] Breakpoint 3, thread_func (arg=0x1) at scheduler_locking_test.c:22 22 printf( "[%u] something of thread(0x%08X) is 0x%08X\n", j, ( unsigned int )pthread_self(), something ); 虽然是源码级调试,由于printf()在本例中是无源码的库函数,在22行step、next都 是单步过函数,前面的显示表明无论step还是next时都发生了线程切换;注意,单步 过函数时其他线程正常执行,是说其他线程有可能被调度到,但不是说一定被调度到, 只是不锁定在当前线程中而已。 (gdb) next [1] something of thread(0xB766EB70) is 0x0B000001 17 for ( i = 0; i < loopcount; i++ ) (gdb) next 19 something = i + j; (gdb) next 20 if ( 0 == i % 0x1000000 ) (gdb) next 17 for ( i = 0; i < loopcount; i++ ) (gdb) info threads 4 Thread 0xb6e6db70 (LWP 842) 0x08048691 in thread_func (arg=0x2) at scheduler_locking_test.c:17 * 3 Thread 0xb766eb70 (LWP 841) thread_func (arg=0x1) at scheduler_locking_test.c:17 2 Thread 0xb7e6fb70 (LWP 840) 0x08048653 in thread_func (arg=0x0) at scheduler_locking_test.c:19 1 Thread 0xb7e706d0 (LWP 837) 0xb7fe4410 in ?? () scheduler-locking为step时非过函数的单步操作会锁定在当前线程中。 一般而言,我们真正需要的是: info threads // 查看多线程 thread // 切换当前线程 set scheduler-locking on // 锁定在当前线程中 这样设置前务必清楚自己在干什么,要确保挂起其他线程不对当前线程产生大的影响, 不要挖坑给自己。比如线程A持有某锁,线程B正在申请该锁,此时挂起线程A,调试线 程B时就会碰上人为制造的死锁,这不叫调试,这叫挖坑。 D: scz 借助前例说一个pstree的事: (gdb) info proc process 837 cmdline = '/tmp/scheduler_locking_test' cwd = '/tmp' exe = '/tmp/scheduler_locking_test' # pstree init-+-NetworkManager |-NetworkManagerD |-acpid |-avahi-daemon---avahi-daemon |-cron |-dbus-daemon |-dhcdbd |-dirmngr |-events/0 |-6*[getty] |-hald---hald-runner-+-hald-addon-acpi | |-hald-addon-inpu | `-2*[hald-addon-stor] |-inetd |-kdm-+-Xorg | `-kdm---kdm_greet |-khelper |-klogd |-ksoftirqd/0 |-kthread-+-aio/0 | |-kacpid | |-kblockd/0 | |-kgameportd | |-kjournald | |-kmirrord | |-kpsmoused | |-kseriod | |-kswapd0 | |-2*[pdflush] | |-scsi_eh_0 | `-vmmemctl |-migration/0 |-portmap |-rpc.statd |-sshd-+-sshd---bash---gdb---scheduler_locki---3*[{scheduler_locki}] | |-sshd---bash | |-sshd---sftp-server | `-sshd---bash---pstree |-syslogd |-udevd `-vmtoolsd 6*[getty]表示有6个名为getty的子进程,3*[{scheduler_locki}]表示有3个子线程。 # pstree -npu 837 scheduler_locki(837)-+-{scheduler_locki}(840) |-{scheduler_locki}(841) `-{scheduler_locki}(842) 对于进程显示的是PID,对于线程显示的是LWP。 2.33 Valgrind使用简介 A: Yusuke Sato http://www.valgrind.org http://www.valgrind.org/info/tools.html http://www.valgrind.org/docs/manual/faq.html http://www.valgrind.org/docs/manual/cl-manual.html http://en.wikipedia.org/wiki/Valgrind 截至2013.10.31最新版本是2012.9.19发布的3.8.1版。 wget http://www.valgrind.org/downloads/valgrind-3.8.1.tar.bz2 tar xvfj valgrind-3.8.1.tar.bz2 cd valgrind-3.8.1 ./configure --help ./configure make make install which valgrind man valgrind -------------------------------------------------------------------------- /* * gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_0 valgrind_test_0.c */ #include #include static unsigned char * foo ( unsigned int size ) { unsigned char *buf; unsigned char *xxx; buf = ( unsigned char * )malloc( size ); if ( NULL != buf ) { buf[size/2] = 0x51; /* * Bug 1. 越界写访问,这是一个单字节溢出。 */ buf[size] = 0x74; } /* * Bug 4. 分配后未被释放的内存,内存泄漏。 */ xxx = ( unsigned char * )malloc( size ); return( buf ); } /* end of foo */ int main ( int argc, char * argv[] ) { unsigned int size = 1024; unsigned char *buf; if ( argc > 1 ) { size = ( unsigned int )strtoul( argv[1], NULL, 0 ); } buf = foo( size ); if ( NULL != buf ) { /* * Bug 2. 越界读访问,这是一个单字节溢出。 */ printf( "buf[%u] = 0x%02X\n", size, buf[size] ); free( buf ); /* * Bug 3. buf已被释放,现在属于访问无效内存,可能成功,可能失败。 */ printf( "buf[%u] = 0x%02X\n", size/2, buf[size/2] ); } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_0 valgrind_test_0.c $ ./valgrind_test_0 buf[1024] = 0x74 buf[512] = 0x51 $ valgrind --tool=memcheck --leak-check=full --leak-resolution=high --show-reachable=yes ./valgrind_test_0 ==12899== Memcheck, a memory error detector ==12899== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==12899== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==12899== Command: ./valgrind_test_0 ==12899== ==12899== Invalid write of size 1 ==12899== at 0x8048492: foo (valgrind_test_0.c:19) ==12899== by 0x80484EE: main (valgrind_test_0.c:37) ==12899== Address 0x4184428 is 0 bytes after a block of size 1,024 alloc'd ==12899== at 0x40255BC: malloc (vg_replace_malloc.c:270) ==12899== by 0x8048474: foo (valgrind_test_0.c:12) ==12899== by 0x80484EE: main (valgrind_test_0.c:37) ==12899== ==12899== Invalid read of size 1 ==12899== at 0x8048505: main (valgrind_test_0.c:43) ==12899== Address 0x4184428 is 0 bytes after a block of size 1,024 alloc'd ==12899== at 0x40255BC: malloc (vg_replace_malloc.c:270) ==12899== by 0x8048474: foo (valgrind_test_0.c:12) ==12899== by 0x80484EE: main (valgrind_test_0.c:37) ==12899== buf[1024] = 0x74 ==12899== Invalid read of size 1 ==12899== at 0x804853A: main (valgrind_test_0.c:48) ==12899== Address 0x4184228 is 512 bytes inside a block of size 1,024 free'd ==12899== at 0x4024FC9: free (vg_replace_malloc.c:446) ==12899== by 0x804852F: main (valgrind_test_0.c:44) ==12899== buf[512] = 0x51 ==12899== ==12899== HEAP SUMMARY: ==12899== in use at exit: 1,024 bytes in 1 blocks ==12899== total heap usage: 2 allocs, 1 frees, 2,048 bytes allocated ==12899== ==12899== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==12899== at 0x40255BC: malloc (vg_replace_malloc.c:270) ==12899== by 0x804849F: foo (valgrind_test_0.c:24) ==12899== by 0x80484EE: main (valgrind_test_0.c:37) ==12899== ==12899== LEAK SUMMARY: ==12899== definitely lost: 1,024 bytes in 1 blocks ==12899== indirectly lost: 0 bytes in 0 blocks ==12899== possibly lost: 0 bytes in 0 blocks ==12899== still reachable: 0 bytes in 0 blocks ==12899== suppressed: 0 bytes in 0 blocks ==12899== ==12899== For counts of detected and suppressed errors, rerun with: -v ==12899== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 11 from 6) 共检测到4处错误: -------------------------------------------------------------------------- Bug 1. valgrind_test_0.c:19 Invalid write of size 1 Address 0x4184428 is 0 bytes after a block of size 1,024 alloc'd Bug 2. valgrind_test_0.c:43 Invalid read of size 1 Address 0x4184428 is 0 bytes after a block of size 1,024 alloc'd Bug 3. valgrind_test_0.c:48 Invalid read of size 1 Address 0x4184228 is 512 bytes inside a block of size 1,024 free'd Bug 4. valgrind_test_0.c:24 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1 -------------------------------------------------------------------------- --tool 缺省就是memcheck --leak-resolution 缺省就是high 前述命令可以简化成: $ valgrind --leak-check=full --show-reachable=yes ./valgrind_test_0 关注这几个命令行参数: --trace-children= [default: no] 跟踪子进程 --track-fds= [default: no] 跟踪那些进程退出时仍处于打开状态的文件描述符(包括套接字) --error-limit= [default: yes] 设为no将取消针对错误数量的阈值,勇猛地报错而不顾忌最终用户的感受 --num-callers= [default: 12] 指定调用栈回溯的层数上限 -------------------------------------------------------------------------- /* * gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_2 valgrind_test_2.c -pthread */ #include #include #include #include static unsigned int count = 0; static pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER; static void * thread_func_a ( void *arg ) { unsigned int i; unsigned int j = ( unsigned int )arg; for ( i = 0; i < j; i++ ) { /* * 多线程访问共享数据但未加锁 */ count += j; /* * 先后申请了两个锁mutex_a、mutex_b,但在thread_func_b()中是按 * mutex_b、mutex_a的顺序申请锁,竞争环境中有可能导致死锁。 */ if ( 0 == pthread_mutex_lock( &mutex_a ) ) { if ( 0 == pthread_mutex_lock( &mutex_b ) ) { count++; pthread_mutex_unlock( &mutex_b ); } pthread_mutex_unlock( &mutex_a ); } } /* end of for */ /* * 多线程访问共享数据但未加锁 */ printf( "count = 0x%08X [%u]\n", count, j ); return( NULL ); } /* end of thread_func_a */ static void * thread_func_b ( void *arg ) { unsigned int i; unsigned int j = ( unsigned int )arg; for ( i = 0; i < j; i++ ) { /* * 多线程访问共享数据但未加锁 */ count -= j; /* * 先后申请了两个锁mutex_b、mutex_a,但在thread_func_a()中是按 * mutex_a、mutex_b的顺序申请锁,竞争环境中有可能导致死锁。 */ if ( 0 == pthread_mutex_lock( &mutex_b ) ) { if ( 0 == pthread_mutex_lock( &mutex_a ) ) { count--; pthread_mutex_unlock( &mutex_a ); } pthread_mutex_unlock( &mutex_b); } } /* end of for */ /* * 多线程访问共享数据但未加锁 */ printf( "count = 0x%08X [%u]\n", count, j ); return( NULL ); } /* end of thread_func_b */ int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; unsigned int thread_num, i; pthread_t *tid = NULL; if ( argc < 2 ) { fprintf( stderr, "Usage: %s \n", argv[0] ); goto main_exit; } thread_num = ( unsigned int )strtoul( argv[1], NULL, 0 ); if ( NULL == ( tid = ( pthread_t * )calloc( thread_num, sizeof( pthread_t ) ) ) ) { perror( "calloc() failed" ); goto main_exit; } for ( i = 0; i < thread_num; i++ ) { if ( 0 == i % 2 ) { pthread_create( &tid[i], NULL, &thread_func_a, ( void * )i ); } else { pthread_create( &tid[i], NULL, &thread_func_b, ( void * )i ); } } for ( i = 0; i < thread_num; i++ ) { pthread_join( tid[i], NULL ); } ret = EXIT_SUCCESS; main_exit: if ( NULL != tid ) { free( tid ); tid = NULL; } return( ret ); } /* end of main */ -------------------------------------------------------------------------- "--tool=helgrind"用于检查多线程相关的BUG。 $ gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_2 valgrind_test_2.c -pthread $ valgrind --tool=helgrind ./valgrind_test_2 4 ==15868== Helgrind, a thread error detector ==15868== Copyright (C) 2007-2012, and GNU GPL'd, by OpenWorks LLP et al. ==15868== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==15868== Command: ./valgrind_test_2 4 ==15868== count = 0x00000000 [0] ==15868== ---Thread-Announcement------------------------------------------ ==15868== ==15868== Thread #3 was created ==15868== at 0x4122E68: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== by 0x404307A: pthread_create@@GLIBC_2.1 (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4029E39: pthread_create_WRK (hg_intercepts.c:255) ==15868== by 0x4029F11: pthread_create@* (hg_intercepts.c:286) ==15868== by 0x8048897: main (valgrind_test_2.c:103) ==15868== ==15868== ---Thread-Announcement------------------------------------------ ==15868== ==15868== Thread #2 was created ==15868== at 0x4122E68: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== by 0x404307A: pthread_create@@GLIBC_2.1 (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4029E39: pthread_create_WRK (hg_intercepts.c:255) ==15868== by 0x4029F11: pthread_create@* (hg_intercepts.c:286) ==15868== by 0x804886A: main (valgrind_test_2.c:99) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Possible data race during write of size 4 at 0x8049B54 by thread #3 ==15868== Locks held: none ==15868== at 0x8048719: thread_func_b (valgrind_test_2.c:55) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== This conflicts with a previous read of size 4 by thread #2 ==15868== Locks held: none ==15868== at 0x80486D7: thread_func_a (valgrind_test_2.c:41) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== count = 0xFFFFFFFE [1] ==15868== ---Thread-Announcement------------------------------------------ ==15868== ==15868== Thread #4 was created ==15868== at 0x4122E68: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== by 0x404307A: pthread_create@@GLIBC_2.1 (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4029E39: pthread_create_WRK (hg_intercepts.c:255) ==15868== by 0x4029F11: pthread_create@* (hg_intercepts.c:286) ==15868== by 0x804886A: main (valgrind_test_2.c:99) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Possible data race during read of size 4 at 0x8049B54 by thread #4 ==15868== Locks held: none ==15868== at 0x8048679: thread_func_a (valgrind_test_2.c:23) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== This conflicts with a previous write of size 4 by thread #3 ==15868== Locks held: none ==15868== at 0x8048719: thread_func_b (valgrind_test_2.c:55) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Possible data race during write of size 4 at 0x8049B54 by thread #4 ==15868== Locks held: none ==15868== at 0x8048681: thread_func_a (valgrind_test_2.c:23) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== This conflicts with a previous read of size 4 by thread #3 ==15868== Locks held: none ==15868== at 0x804876F: thread_func_b (valgrind_test_2.c:73) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Thread #4: lock order "0x8049B70 before 0x8049B58" violated ==15868== ==15868== Observed (incorrect) order is: acquisition of lock at 0x8049B58 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048691: thread_func_a (valgrind_test_2.c:28) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== followed by a later acquisition of lock at 0x8049B70 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x80486A1: thread_func_a (valgrind_test_2.c:30) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== Required order was established by acquisition of lock at 0x8049B70 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048729: thread_func_b (valgrind_test_2.c:60) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== followed by a later acquisition of lock at 0x8049B58 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048739: thread_func_b (valgrind_test_2.c:62) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Lock at 0x8049B70 was first observed ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048729: thread_func_b (valgrind_test_2.c:60) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== Lock at 0x8049B58 was first observed ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048739: thread_func_b (valgrind_test_2.c:62) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== Possible data race during write of size 4 at 0x8049B54 by thread #4 ==15868== Locks held: 2, at addresses 0x8049B70 0x8049B58 ==15868== at 0x80486AE: thread_func_a (valgrind_test_2.c:32) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== This conflicts with a previous read of size 4 by thread #3 ==15868== Locks held: none ==15868== at 0x804876F: thread_func_b (valgrind_test_2.c:73) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== count = 0x00000004 [2] ==15868== ---Thread-Announcement------------------------------------------ ==15868== ==15868== Thread #5 was created ==15868== at 0x4122E68: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== by 0x404307A: pthread_create@@GLIBC_2.1 (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4029E39: pthread_create_WRK (hg_intercepts.c:255) ==15868== by 0x4029F11: pthread_create@* (hg_intercepts.c:286) ==15868== by 0x8048897: main (valgrind_test_2.c:103) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Possible data race during read of size 4 at 0x8049B54 by thread #5 ==15868== Locks held: none ==15868== at 0x8048711: thread_func_b (valgrind_test_2.c:55) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== This conflicts with a previous write of size 4 by thread #4 ==15868== Locks held: none ==15868== at 0x8048681: thread_func_a (valgrind_test_2.c:23) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Thread #5: lock order "0x8049B58 before 0x8049B70" violated ==15868== ==15868== Observed (incorrect) order is: acquisition of lock at 0x8049B70 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048729: thread_func_b (valgrind_test_2.c:60) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== followed by a later acquisition of lock at 0x8049B58 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048739: thread_func_b (valgrind_test_2.c:62) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== Required order was established by acquisition of lock at 0x8049B58 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048691: thread_func_a (valgrind_test_2.c:28) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== followed by a later acquisition of lock at 0x8049B70 ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x80486A1: thread_func_a (valgrind_test_2.c:30) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== ---------------------------------------------------------------- ==15868== ==15868== Lock at 0x8049B70 was first observed ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048729: thread_func_b (valgrind_test_2.c:60) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== Lock at 0x8049B58 was first observed ==15868== at 0x4026A1C: pthread_mutex_lock (hg_intercepts.c:495) ==15868== by 0x8048739: thread_func_b (valgrind_test_2.c:62) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== Possible data race during write of size 4 at 0x8049B54 by thread #5 ==15868== Locks held: 2, at addresses 0x8049B70 0x8049B58 ==15868== at 0x8048746: thread_func_b (valgrind_test_2.c:64) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== ==15868== This conflicts with a previous read of size 4 by thread #4 ==15868== Locks held: none ==15868== at 0x80486D7: thread_func_a (valgrind_test_2.c:41) ==15868== by 0x4029F90: mythread_wrapper (hg_intercepts.c:219) ==15868== by 0x4042954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==15868== by 0x4122E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==15868== count = 0xFFFFFFF8 [3] ==15868== ==15868== For counts of detected and suppressed errors, rerun with: -v ==15868== Use --history-level=approx or =none to gain increased speed, at ==15868== the cost of reduced accuracy of conflicting-access information ==15868== ERROR SUMMARY: 18 errors from 8 contexts (suppressed: 175 from 59) 搜索"Locks held: none",表示存在无锁情况下访问共享数据的情形,这是多线程编 程时的经典BUG。 搜索"lock order",一般是类似这种提示信息: lock order "0x8049B70 before 0x8049B58" violated 两个16进制地址对应两个锁,上面这句话的意思是,应该先申请0x8049B70处的锁, 再申请0x8049B58处的锁,但实际反过来了。使用多锁时申请顺序不统一,线程A以一 种顺序申请多锁,线程B以另一种顺序申请多锁,在竞争环境中有可能导致死锁。 与"--tool=helgrind"类似,"--tool=drd"也用于检查多线程相关的BUG,对于绝大多 数程序来说,后者消耗的内存更少。 $ valgrind --tool=drd ./valgrind_test_2 4 ==18249== drd, a thread error detector ==18249== Copyright (C) 2006-2012, and GNU GPL'd, by Bart Van Assche. ==18249== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==18249== Command: ./valgrind_test_2 4 ==18249== count = 0x00000000 [0] ==18249== Thread 3: ==18249== Conflicting store by thread 3 at 0x08049b54 size 4 ==18249== at 0x8048719: thread_func_b (valgrind_test_2.c:55) ==18249== by 0x402E308: vgDrd_thread_wrapper (drd_pthread_intercepts.c:355) ==18249== by 0x4049954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==18249== by 0x4129E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==18249== Allocation context: BSS section of /tmp/valgrind_test_2 ==18249== Other segment start (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== ==18249== Conflicting store by thread 3 at 0x08049b54 size 4 ==18249== at 0x8048746: thread_func_b (valgrind_test_2.c:64) ==18249== by 0x402E308: vgDrd_thread_wrapper (drd_pthread_intercepts.c:355) ==18249== by 0x4049954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==18249== by 0x4129E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==18249== Allocation context: BSS section of /tmp/valgrind_test_2 ==18249== Other segment start (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== count = 0xFFFFFFFE [1] ==18249== Thread 4: ==18249== Conflicting load by thread 4 at 0x08049b54 size 4 ==18249== at 0x8048679: thread_func_a (valgrind_test_2.c:23) ==18249== by 0x402E308: vgDrd_thread_wrapper (drd_pthread_intercepts.c:355) ==18249== by 0x4049954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==18249== by 0x4129E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==18249== Allocation context: BSS section of /tmp/valgrind_test_2 ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== ==18249== Conflicting store by thread 4 at 0x08049b54 size 4 ==18249== at 0x8048681: thread_func_a (valgrind_test_2.c:23) ==18249== by 0x402E308: vgDrd_thread_wrapper (drd_pthread_intercepts.c:355) ==18249== by 0x4049954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==18249== by 0x4129E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==18249== Allocation context: BSS section of /tmp/valgrind_test_2 ==18249== Other segment start (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== ==18249== Conflicting store by thread 4 at 0x08049b54 size 4 ==18249== at 0x80486AE: thread_func_a (valgrind_test_2.c:32) ==18249== by 0x402E308: vgDrd_thread_wrapper (drd_pthread_intercepts.c:355) ==18249== by 0x4049954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==18249== by 0x4129E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==18249== Allocation context: BSS section of /tmp/valgrind_test_2 ==18249== Other segment start (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 2) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== count = 0x00000004 [2] ==18249== Thread 5: ==18249== Conflicting load by thread 5 at 0x08049b54 size 4 ==18249== at 0x8048711: thread_func_b (valgrind_test_2.c:55) ==18249== by 0x402E308: vgDrd_thread_wrapper (drd_pthread_intercepts.c:355) ==18249== by 0x4049954: start_thread (in /lib/i686/cmov/libpthread-2.11.2.so) ==18249== by 0x4129E7D: clone (in /lib/i686/cmov/libc-2.11.2.so) ==18249== Allocation context: BSS section of /tmp/valgrind_test_2 ==18249== Other segment start (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 3) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment start (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== Other segment end (thread 4) ==18249== (thread finished, call stack no longer available) ==18249== count = 0xFFFFFFF8 [3] ==18249== ==18249== For counts of detected and suppressed errors, rerun with: -v ==18249== ERROR SUMMARY: 14 errors from 6 contexts (suppressed: 240 from 90) 搜索"Conflicting store",表示存在无锁情况下写共享数据的情形。搜索 "Conflicting load",表示存在无锁情况下读共享数据的情形。 "--tool=drd"未能检查出使用多锁时申请顺序不统一的问题,"--tool=helgrind"则 可以。如果只是检查无锁情况下访问共享数据,就用drd,否则用helgrind。 -------------------------------------------------------------------------- /* * gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_3 valgrind_test_3.c -ldl */ #include #include #include static void foo ( void ) { void *handle; while ( 1 ) { handle = dlopen( "/usr/lib/libm.so", RTLD_LAZY ); if ( !handle ) { fprintf( stderr, "dlopen() failed\n" ); break; } else { dlclose( handle ); sleep( 100 ); } } /* end of while */ return; } /* end of foo */ int main ( int argc, char * argv[] ) { foo(); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- 过去有个低版本glibc中的local_strdup()函数用malloc()分配了一段内存,但没有 对应的free(): /* * 申请4字节内存 */ malloc( sizeof( struct link_map * ) ); dlopen()最终会调用local_strdup()。在受影响的glibc环境中,valgrind_test_3.c 每调用一次dlopen()会泄漏若干字节(与堆分配机制相关)的内存。像这种内存泄漏, 不是valgrind_test_3.c本身的BUG,甚至不是libdl.so的BUG,这是glibc的BUG,非 常隐蔽,检查用户代码是查不出什么问题的。但是用Valgrind可以发现这种内存泄漏。 从diff信息中看到,至少2007.12.12该BUG存在,2008.7.26修补了该BUG。参看: https://sourceware.org/cgi-bin/cvsweb.cgi/libc/elf/dl-load.c.diff?r1=1.249.2.31&r2=1.290&cvsroot=glibc&f=h 我手头的glibc不存在该BUG,无法演示Valgrind的输出。 $ gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_3 valgrind_test_3.c -ldl $ valgrind --leak-check=full --show-reachable=yes ./valgrind_test_3 可以用top命令观察内存使用情况: top -d 1 -b -p `pidof valgrind_test_3` | grep valgrind_test_3 top -d 5 -n 50 -b -p `pidof valgrind_test_3` | grep valgrind_test_3 | awk '{print $5,$6,$7,$10;}' 对于快速增长的内存泄漏,top还是可以发现的,作为一种辅助手段吧。 A: scz 2013-11-05 09:19 Valgrind受实现方式(模拟执行)的制约,不能Attach到已经运行中的进程上,这个限 制还是蛮大的。 "--tool=callgrind"用于性能剖析,参看: http://www.valgrind.org/docs/manual/cl-manual.html 这次我们用smbd举例: [root@ /root/src]> wget http://samba.org/samba/ftp/stable/samba-3.6.1.tar.gz [root@ /root/src]> tar xvfz samba-3.6.1.tar.gz [root@ /root/src/samba-3.6.1/source3]> ./configure --enable-debug --enable-developer --disable-pie [root@ /root/src/samba-3.6.1/source3]> make bin/smbd (这个省时间) [root@ /root/src/samba-3.6.1/source3/bin]> touch /tmp/smb.conf [root@ /root/src/samba-3.6.1/source3/bin]> valgrind --tool=callgrind --dump-instr=yes --collect-jumps=yes ./smbd -s /tmp/smb.conf 从一台Windows上访问位于192.168.7.20的smbd,触发一些真实的代码逻辑: > net use \\192.168.7.20\ipc$ "" /u:"" The command completed successfully. > net use \\192.168.7.20\ipc$ /d \\192.168.7.20\ipc$ was deleted successfully. 当被剖析进程结束时会产生callgrind.out.文件: [root@ /root/src/samba-3.6.1/source3/bin]> ls -l callgrind.out.* -rw------- 1 root root 587633 11月 6 14:29 callgrind.out.15158 -rw------- 1 root root 0 11月 6 14:29 callgrind.out.15164 -rw------- 1 root root 0 11月 6 14:29 callgrind.out.15166 -rw------- 1 root root 1803935 11月 6 14:31 callgrind.out.15243 [root@ /root/src/samba-3.6.1/source3/bin]> ps auwx | grep smbd root 15164 0.3 4.4 99268 45948 ? Ss 14:29 0:00 /usr/bin/valgrind.bin --tool=callgrind --dump-instr=yes --collect-jumps=yes ./smbd -s /tmp/smb.conf root 15166 0.0 4.1 95336 42996 ? S 14:29 0:00 /usr/bin/valgrind.bin --tool=callgrind --dump-instr=yes --collect-jumps=yes ./smbd -s /tmp/smb.conf [root@ /root/src/samba-3.6.1/source3/bin]> kill 15164 [root@ /root/src/samba-3.6.1/source3/bin]> ps auwx | grep smbd [root@ /root/src/samba-3.6.1/source3/bin]> ls -l callgrind.out.* -rw------- 1 root root 587633 11月 6 14:29 callgrind.out.15158 -rw------- 1 root root 1446244 11月 6 14:37 callgrind.out.15164 -rw------- 1 root root 1409533 11月 6 14:37 callgrind.out.15166 -rw------- 1 root root 1803935 11月 6 14:31 callgrind.out.15243 这里要注意smbd有fork()行为,每次TCP连接会产生新的子进程。 推荐安装kcachegrind,用于查看callgrind.out.文件。 # apt-get install kcachegrind # which kcachegrind /usr/bin/kcachegrind kcachegrind是一个GUI程序,需要XWindow环境。打开callgrind.out.之后就会 发现,这何止是性能剖析,这是完整的执行流程跟踪,真正的Linux调试利器。受制 于文本描述,本文无法展示kcachegrind的震撼效果,建议读者自行尝试。 D: zyh 2013-11-15 windows port of kcachegrind http://sourceforge.net/projects/precompiledbin/ Windows版kcachegrind只能看性能剖析数据,"--dump-instr=yes --collect-jumps=yes" 产生的数据无法使用。很显然"Source Code"页找不到源码,"Machine Code"页则需 要调用: objdump -C -d --start-address=... --stop-address=... D: scz 2013-11-14 12:18 用"aptitude install valgrind"安装的valgrind版本才3.7.0,还是手工安装最新版 吧。 wget http://www.valgrind.org/downloads/valgrind-3.8.1.tar.bz2 tar xvfj valgrind-3.8.1.tar.bz2 cd valgrind-3.8.1 rm -rf build mkdir build cd build ../configure --help ../configure --program-prefix='scz-' --program-suffix='-3.8.1' -v make -j 6 (第一次会报个错,不用管它,再次make -j 6) make -j 6 make install (对应的是make uninstall) which scz-valgrind-3.8.1 (/usr/local/bin/scz-valgrind-3.8.1) 本想用下面这条命令修改安装后的valgrind名字,编译gdb时就这么干的,不知什么 原因没有成功: ../configure --program-transform-name='s,\(.*\),scz-\1-3.8.1,' -v 后来换成: ../configure --program-prefix='scz-' --program-suffix='-3.8.1' -v 2.34 用Mudflap检查内存错误 A: Yusuke Sato Valgrind对DATA、BSS、STACK中的越界读写不敏感,参看: Why doesn't Memcheck find the array overruns in this program? http://www.valgrind.org/docs/manual/faq.html#faq.overruns -------------------------------------------------------------------------- /* * gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_1 valgrind_test_1.c * gcc-4.4 -Wall -pipe -O0 -g -fmudflap -o valgrind_test_1_mudflap valgrind_test_1.c -lmudflap */ static unsigned char buf_a[1024]; static void foo ( void ) { unsigned char buf_b[128]; unsigned char c; /* * Bug 1. BSS中的越界读访问 */ c = buf_a[1024]; /* * Bug 2. BSS中的越界写访问 */ buf_a[1024] = 0x51; /* * Bug 3. STACK中的越界读访问 */ c = buf_b[128]; /* * Bug 4. STACK中的越界写访问 */ buf_b[128] = 0x74; return; } /* end of foo */ int main ( int argc, char * argv[] ) { foo(); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_1 valgrind_test_1.c $ valgrind --leak-check=full --show-reachable=yes ./valgrind_test_1 ==13048== Memcheck, a memory error detector ==13048== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==13048== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==13048== Command: ./valgrind_test_1 ==13048== ==13048== ==13048== HEAP SUMMARY: ==13048== in use at exit: 0 bytes in 0 blocks ==13048== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==13048== ==13048== All heap blocks were freed -- no leaks are possible ==13048== ==13048== For counts of detected and suppressed errors, rerun with: -v ==13048== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 11 from 6) valgrind什么也没查出来,试了传说中的"--tool=exp-sgcheck",仍然没查出什么: $ valgrind --tool=exp-sgcheck ./valgrind_test_1 ==13054== exp-sgcheck, a stack and global array overrun detector ==13054== NOTE: This is an Experimental-Class Valgrind Tool ==13054== Copyright (C) 2003-2012, and GNU GPL'd, by OpenWorks Ltd et al. ==13054== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==13054== Command: ./valgrind_test_1 ==13054== ==13054== ==13054== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) Mudflap可以查出valgrind_test_1.c的问题,但需要GCC 4和libmudflap的支持。为 了使用Mudflap,需要重新编译源代码,Valgrind可没这么变态。 # aptitude install libmudflap0-4.4-dev # aptitude install libmudflap0-dbg libmudflap0-dbg不是必需的,但安装后在gdb中可以看更多符号信息,建议安装。 $ gcc-4.4 -Wall -pipe -O0 -g -fmudflap -o valgrind_test_1_mudflap valgrind_test_1.c -lmudflap 如果目标是多线程程序,应该使用-fmudflapth和-lmudflapth。 $ ./valgrind_test_1_mudflap ******* mudflap violation 1 (check/read): time=1383209946.636299 ptr=0x80c9180 size=1025 pc=0xb7e573bd location=`valgrind_test_1.c:15:5 (foo)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7e573bd] ./valgrind_test_1_mudflap() [0x8048816] ./valgrind_test_1_mudflap(main+0xb) [0x804899c] Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80ca1e8: name=`valgrind_test_1.c:5:25 buf_a' bounds=[0x80c9180,0x80c957f] size=1024 area=static check=3r/0w liveness=3 alloc time=1383209946.636268 pc=0xb7e56b2d number of nearby objects: 1 ******* mudflap violation 2 (check/write): time=1383209946.636929 ptr=0x80c9180 size=1025 pc=0xb7e573bd location=`valgrind_test_1.c:19:5 (foo)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7e573bd] ./valgrind_test_1_mudflap() [0x8048880] ./valgrind_test_1_mudflap(main+0xb) [0x804899c] Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80ca1e8: name=`valgrind_test_1.c:5:25 buf_a' number of nearby objects: 1 ******* mudflap violation 3 (check/read): time=1383209946.637139 ptr=0xbff8ab0f size=129 pc=0xb7e573bd location=`valgrind_test_1.c:23:5 (foo)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7e573bd] ./valgrind_test_1_mudflap() [0x80488ef] ./valgrind_test_1_mudflap(main+0xb) [0x804899c] Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80cac98: name=`valgrind_test_1.c:9:21 (foo) buf_b' bounds=[0xbff8ab0f,0xbff8ab8e] size=128 area=stack check=3r/0w liveness=3 alloc time=1383209946.636291 pc=0xb7e56b2d number of nearby objects: 1 ******* mudflap violation 4 (check/write): time=1383209946.637349 ptr=0xbff8ab0f size=129 pc=0xb7e573bd location=`valgrind_test_1.c:27:5 (foo)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7e573bd] ./valgrind_test_1_mudflap() [0x8048957] ./valgrind_test_1_mudflap(main+0xb) [0x804899c] Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80cac98: name=`valgrind_test_1.c:9:21 (foo) buf_b' number of nearby objects: 1 Mudflap的输出比Valgrind难看,每行"*******"引领一个新的"mudflap violation"。 "location="和"name="给出了源码级的信息: location=`valgrind_test_1.c:15:5 (foo)' name=`valgrind_test_1.c:5:25 buf_a' 意思是15行、5列的代码(位于foo()中)操作了5行、25列的变量(buf_a)。 暂时禁用Mudflap: $ MUDFLAP_OPTIONS="-mode-nop" ./valgrind_test_1_mudflap 禁止以时间戳的形式跟踪"mudflap object"的生命周期,同时禁止调用栈回溯,这样 可以部分减少Mudflap引起的性能下降: $ MUDFLAP_OPTIONS="-no-timestamps -backtrace=0" ./valgrind_test_1_mudflap $ MUDFLAP_OPTIONS="-no-timestamps -no-backtrace" ./valgrind_test_1_mudflap ******* mudflap violation 1 (check/read): time=1383211342.643393 ptr=0x80c9180 size=1025 pc=0xb7e5e3bd location=`valgrind_test_1.c:15:5 (foo)' Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80ca1e8: name=`valgrind_test_1.c:5:25 buf_a' bounds=[0x80c9180,0x80c957f] size=1024 area=static check=3r/0w liveness=3 alloc time=0.000000 pc=0xb7e5db2d number of nearby objects: 1 ******* mudflap violation 2 (check/write): time=1383211342.643870 ptr=0x80c9180 size=1025 pc=0xb7e5e3bd location=`valgrind_test_1.c:19:5 (foo)' Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80ca1e8: name=`valgrind_test_1.c:5:25 buf_a' number of nearby objects: 1 ******* mudflap violation 3 (check/read): time=1383211342.644019 ptr=0xbffe2b2f size=129 pc=0xb7e5e3bd location=`valgrind_test_1.c:23:5 (foo)' Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80cad00: name=`valgrind_test_1.c:9:21 (foo) buf_b' bounds=[0xbffe2b2f,0xbffe2bae] size=128 area=stack check=3r/0w liveness=3 alloc time=0.000000 pc=0xb7e5db2d number of nearby objects: 1 ******* mudflap violation 4 (check/write): time=1383211342.644151 ptr=0xbffe2b2f size=129 pc=0xb7e5e3bd location=`valgrind_test_1.c:27:5 (foo)' Nearby object 1: checked region begins 0B into and ends 1B after mudflap object 0x80cad00: name=`valgrind_test_1.c:9:21 (foo) buf_b' number of nearby objects: 1 "-backtrace=0"与"-no-backtrace"等效。用Beyond Compare比较前后两次输出,第 二次输出中始终是"alloc time=0.000000",第一次输出中"alloc time="有时间戳。 第二次输出中没有调用栈回溯信息,我觉得这个消失的调用栈回溯信息真没啥用,完 全不是你想像的那种,在-g编译的情况下居然没有以符号形式显示调用栈回溯。 参看: Mudflap Pointer Debugging http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging 有个简单办法查看MUDFLAP_OPTIONS,指定一个不存在的选项,出错信息里就有帮助: $ MUDFLAP_OPTIONS="nonexist" ./valgrind_test_1_mudflap warning: unrecognized string 'nonexist' in mudflap options This is a single-threaded thread-unaware GCC "mudflap" memory-checked binary. Mudflap is Copyright (C) 2002-2010 Free Software Foundation, Inc. The mudflap code can be controlled by an environment variable: $ export MUDFLAP_OPTIONS='' $ where is a space-separated list of any of the following options. Use `-no-OPTION' to disable options. -mode-nop mudflaps do nothing -mode-populate mudflaps populate object tree -mode-check mudflaps check for memory violations [active] -mode-violate mudflaps always cause violations (diagnostic) -viol-nop violations do not change program execution [active] -viol-abort violations cause a call to abort() -viol-segv violations are promoted to SIGSEGV signals -viol-gdb violations fork a gdb process attached to current program -trace-calls trace calls to mudflap runtime library -verbose-trace trace internal events within mudflap runtime library -collect-stats collect statistics on mudflap's operation -sigusr1-report print report upon SIGUSR1 -internal-checking perform more expensive internal checking -print-leaks print any memory leaks at program shutdown -libc-freeres call glibc __libc_freeres at shutdown for better leak data [active] -check-initialization detect uninitialized object reads -verbose-violations print verbose messages when memory violations occur [active] -abbreviate abbreviate repetitive listings [active] -timestamps track object lifetime timestamps [active] -ignore-reads ignore read accesses - assume okay -wipe-stack wipe stack objects at unwind -wipe-heap wipe heap objects at free -heur-proc-map support /proc/self/map heuristics -heur-stack-bound enable a simple upper stack bound heuristic -heur-start-end support _start.._end heuristics -heur-stdlib register standard library data (argv, errno, stdin, ...) [active] -free-queue-length=N queue N deferred free() calls before performing them [4] -persistent-count=N keep a history of N unregistered regions [100] -crumple-zone=N surround allocations with crumple zones of N bytes [32] -lc-adapt=N adapt mask/shift parameters after N cache misses [1000003] -backtrace=N keep an N-level stack trace of each call context [4] [active]表示缺省启用。 A: scz 2013-11-04 10:54 用IDA研究了一下Mudflap输出的调用栈回溯为什么没有符号信息。原来用Mudflap机 制时实际上向源代码中插入了新代码,"location="的值是__mf_check()的形参,使 用Mudflap机制后已经无法给出原始代码的符号信息。 Mudflap有个缺省未启用的"-print-leaks"用于检查内存泄漏,不过实在不好用。 -------------------------------------------------------------------------- /* * gcc-4.4 -DUSE_MTRACE -Wall -pipe -O0 -g -o valgrind_test_6 valgrind_test_6.c * gcc-4.4 -Wall -pipe -O0 -g -fmudflap -o valgrind_test_6_mudflap valgrind_test_6.c -lmudflap */ #include #ifdef USE_MTRACE #include #endif static void foo ( unsigned int size ) { unsigned char *buf; unsigned int i, j, k; buf = ( unsigned char * )malloc( size ); if ( buf ) { /* * 错误!单字节溢出(off-by-one) */ buf[size] = 0x74; } for ( i = 0; i < 1024; i++ ) { j = i % 2; k = i * 2; } if ( buf ) { free( buf ); } return; } /* end of foo */ static void bar ( unsigned int size ) { void *buf_a, *buf_b; buf_a = malloc( size ); /* * 没有相应的释放buf_b的代码 */ buf_b = malloc( size ); if ( buf_a ) { free( buf_a ); } return; } /* end of bar */ int main ( int argc, char * argv[] ) { unsigned int size = 1024; #ifdef USE_MTRACE mtrace(); #endif if ( argc > 1 ) { size = ( unsigned int )strtoul( argv[1], NULL, 0 ); } foo( size ); bar( size ); #ifdef USE_MTRACE muntrace(); #endif return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.4 -Wall -pipe -O0 -g -fmudflap -o valgrind_test_6_mudflap valgrind_test_6.c -lmudflap $ MUDFLAP_OPTIONS="-internal-checking -print-leaks" ./valgrind_test_6_mudflap ******* mudflap violation 1 (check/write): time=1383534623.844980 ptr=0x80cb098 size=1 pc=0xb7e6d3bd location=`valgrind_test_6.c:22:9 (foo)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7e6d3bd] ./valgrind_test_6_mudflap() [0x8048837] ./valgrind_test_6_mudflap(main+0xbe) [0x804897a] Nearby object 1: checked region begins 1B after and ends 1B after mudflap object 0x80cb0e0: name=`malloc region' bounds=[0x80cac98,0x80cb097] size=1024 area=heap check=0r/0w liveness=0 alloc time=1383534623.844779 pc=0xb7e6cb2d /usr/lib/libmudflap.so.0(__mf_register+0x3d) [0xb7e6cb2d] /usr/lib/libmudflap.so.0(__wrap_malloc+0xd9) [0xb7e6e2f9] ./valgrind_test_6_mudflap() [0x80487d5] ./valgrind_test_6_mudflap(main+0xbe) [0x804897a] number of nearby objects: 1 Leaked object 1: mudflap object 0x80cc170: name=`malloc region' bounds=[0x80cbd48,0x80cc147] size=1024 area=heap check=0r/0w liveness=0 alloc time=1383534623.845748 pc=0xb7e6cb2d /usr/lib/libmudflap.so.0(__mf_register+0x3d) [0xb7e6cb2d] /usr/lib/libmudflap.so.0(__wrap_malloc+0xd9) [0xb7e6e2f9] ./valgrind_test_6_mudflap() [0x80488a6] ./valgrind_test_6_mudflap(main+0xca) [0x8048986] number of leaked objects: 1 从"location="知道有一个单字节溢出位于22行9列。从"Leaked object"知道有内存 泄漏,但从哪儿泄漏的呢?在IDA中查看0x80488a6倒是定位了bar()中的第二个malloc(), 但也太扯了。 $ gdb -q ./valgrind_test_6_mudflap Reading symbols from /tmp/valgrind_test_6_mudflap...done. (gdb) set environment MUDFLAP_OPTIONS=-internal-checking -print-leaks -viol-segv (gdb) start Temporary breakpoint 1 at 0x80488c8: file valgrind_test_6.c, line 53. Starting program: /tmp/valgrind_test_6_mudflap Temporary breakpoint 1, main (argc=1, argv=0xbffffac4) at valgrind_test_6.c:53 53 int main ( int argc, char * argv[] ) (gdb) info signals SIGSEGV Signal Stop Print Pass to program Description SIGSEGV Yes Yes Yes Segmentation fault (gdb) c Continuing. ******* mudflap violation 1 (check/write): time=1383540511.628719 ptr=0x80cb100 size=1 pc=0xb7ef23bd location=`valgrind_test_6.c:22:9 (foo)' /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7ef23bd] /tmp/valgrind_test_6_mudflap() [0x8048837] /tmp/valgrind_test_6_mudflap(main+0xbe) [0x804897a] Nearby object 1: checked region begins 1B after and ends 1B after mudflap object 0x80cb148: name=`malloc region' bounds=[0x80cad00,0x80cb0ff] size=1024 area=heap check=0r/0w liveness=0 alloc time=1383540511.625154 pc=0xb7ef1b2d /usr/lib/libmudflap.so.0(__mf_register+0x3d) [0xb7ef1b2d] /usr/lib/libmudflap.so.0(__wrap_malloc+0xd9) [0xb7ef32f9] /tmp/valgrind_test_6_mudflap() [0x80487d5] /tmp/valgrind_test_6_mudflap(main+0xbe) [0x804897a] number of nearby objects: 1 Program received signal SIGSEGV, Segmentation fault. 0xb7fe4410 in ?? () (gdb) bt #0 0xb7fe4410 in ?? () #1 0xb7ef22f1 in __mfu_check (ptr=0x80cb100, sz=1, type=1, location=0x8048a70 "valgrind_test_6.c:22:9 (foo)") at ../../../src/libmudflap/mf-runtime.c:1037 #2 0xb7ef23bd in __mf_check (ptr=0x80cb100, sz=1, type=1, location=0x8048a70 "valgrind_test_6.c:22:9 (foo)") at ../../../src/libmudflap/mf-runtime.c:816 #3 0x08048837 in foo (size=1024) at valgrind_test_6.c:11 #4 0x0804897a in main (argc=1, argv=0xbffffac4) at valgrind_test_6.c:66 (gdb) signal 0 Continuing with no signal. Leaked object 1: mudflap object 0x80cc1f0: name=`malloc region' bounds=[0x80cbdc8,0x80cc1c7] size=1024 area=heap check=0r/0w liveness=0 alloc time=1383540521.993654 pc=0xb7ef1b2d /usr/lib/libmudflap.so.0(__mf_register+0x3d) [0xb7ef1b2d] /usr/lib/libmudflap.so.0(__wrap_malloc+0xd9) [0xb7ef32f9] /tmp/valgrind_test_6_mudflap() [0x80488a6] /tmp/valgrind_test_6_mudflap(main+0xca) [0x8048986] number of leaked objects: 1 Program exited normally. (gdb) 本来想通过"-viol-segv"捕捉SIGSEGV,进而在GDB中查看调用栈回溯。对于单字节溢 出,这个目的达到了,对于内存泄漏,Mudflap并不发送SIGSEGV。看"-print-leaks" 的说明,是"print any memory leaks at program shutdown",应该是只有当进程结 束时才好判断是否有内存泄漏,之前不好下这个结论。Mudflap在诊断内存泄漏时鸡 肋得不行,什么破玩意儿。目前能想到的招,先执行一次valgrind_test_6_mudflap 获得内存泄漏的调用栈回溯,然后在IDA中看,或者在GDB中"list *0x80488a6"。 $ gdb -q ./valgrind_test_6_mudflap Reading symbols from /tmp/valgrind_test_6_mudflap...done. (gdb) list *0x80488a6 0x80488a6 is in bar (valgrind_test_6.c:45). 40 41 buf_a = malloc( size ); 42 /* 43 * 没有相应的释放buf_b的代码 44 */ 45 buf_b = malloc( size ); 46 if ( buf_a ) 47 { 48 free( buf_a ); 49 } 2.35 如何检查内存泄漏 A: scz 2013-11-04 17:02 至少可以尝试如下几种办法,但这不是全集,还有很多未在此处罗列。 1) valgrind --leak-check=full --show-reachable=yes 2) top一般输出如下列: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND -------------------------------------------------------------------------- VIRT 进程占用的虚拟内存,包括该进程空间中动态库占用的内存,以KB为单位。与 RES的区别在于包含SWAP。 VIRT = SWAP + RES VIRT = SWAP + CODE + DATA RES 进程占用的物理内存,以KB为单位。 RES = CODE + DATA SHR 进程占用的共享内存,这种内存一般在多个进程间共享,以KB为单位。 %MEM 进程的物理内存使用率 -------------------------------------------------------------------------- 用如下命令持续监视指定进程的内存使用情况: top -d 5 -b -p `pidof ` | grep "-d 5"表示每5秒采一次样,缺省是3秒。上述命令后面无法接"| awk ...",没有输 出,不知何故。如下命令表示每5秒采一次样,持续采样50次: top -d 5 -n 50 -b -p `pidof ` | grep | awk '{print $5,$6,$7,$10;}' 这次由于top会结束,后面的awk有输出,不过要等到top结束时才有输出。 相比valgrind,top只能发现比较明显的内存泄漏,对于那些极其缓慢的内存泄漏, top并不适用。 3) 查看mtrace(1)、mtrace(3)。 a. 在代码中嵌入mtrace()以监控之后出现的malloc(3)、free(3)等函数 b. 环境变量MALLOC_TRACE指向一个可写日志文件 c. 执行程序,日志文件中会有内容出现 d. 用"mtrace "解读日志文件 过去有一些程序员会自己封装malloc()、free()以跟踪内存泄漏,有了mtrace就不必 这样山寨了。 4) 用Mudflap。 a. gcc-4.4 ... -fmudflap ... -lmudflap b. MUDFLAP_OPTIONS="-internal-checking -print-leaks" c. 查看"Leaked object"的调用栈回溯 d. 用IDA或者在GDB中"list *0x..." 受制于实现方式,用Mudflap时很难直接获取出错点的源码级位置信息。 2.36 用MALLOC_CHECK_检查内存错误 A: scz 2013-11-01 15:44 在malloc(3)、mallopt(3)中搜索MALLOC_CHECK_和M_CHECK_ACTION。 从glibc 2.x开始,可以通过环境变量MALLOC_CHECK_影响malloc()的行为: 0 禁用该特性 1 检查到错误时向stderr输出详细诊断信息并继续执行 2 检查到错误时立即调用abort() 3 检查到错误时向stderr输出详细诊断信息、调用栈回溯、内存布局,并调用abort() 5 检查到错误时向stderr输出简单诊断信息并继续执行 7 检查到错误时向stderr输出简单诊断信息、调用栈回溯、内存布局,并调用abort() 当MALLOC_CHECK_非0时,检查二次释放(double free)、单字节溢出(off-by-one)等 内存错误。 出于安全原因,缺省情况下MALLOC_CHECK_对SUID/SGID程序无效,但如果/etc/suid-debug 文件存在(内容任意),MALLOC_CHECK_对SUID/SGID程序有效。 -------------------------------------------------------------------------- /* * gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_4 valgrind_test_4.c */ #include static void foo ( unsigned int size ) { void *buf; buf = malloc( size ); if ( buf ) { free( buf ); /* * 错误!二次释放 */ free( buf ); } return; } /* end of foo */ int main ( int argc, char * argv[] ) { unsigned int size = 1024; if ( argc > 1 ) { size = ( unsigned int )strtoul( argv[1], NULL, 0 ); } foo( size ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_4 valgrind_test_4.c $ valgrind --leak-check=full --show-reachable=yes ./valgrind_test_4 ==16745== Memcheck, a memory error detector ==16745== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==16745== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==16745== Command: ./valgrind_test_4 ==16745== ==16745== Invalid free() / delete / delete[] / realloc() ==16745== at 0x4024FC9: free (vg_replace_malloc.c:446) ==16745== by 0x8048463: foo (valgrind_test_4.c:17) ==16745== by 0x80484AC: main (valgrind_test_4.c:30) ==16745== Address 0x4184028 is 0 bytes inside a block of size 1,024 free'd ==16745== at 0x4024FC9: free (vg_replace_malloc.c:446) ==16745== by 0x8048458: foo (valgrind_test_4.c:13) ==16745== by 0x80484AC: main (valgrind_test_4.c:30) ==16745== ==16745== ==16745== HEAP SUMMARY: ==16745== in use at exit: 0 bytes in 0 blocks ==16745== total heap usage: 1 allocs, 2 frees, 1,024 bytes allocated ==16745== ==16745== All heap blocks were freed -- no leaks are possible ==16745== ==16745== For counts of detected and suppressed errors, rerun with: -v ==16745== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 11 from 6) $ MALLOC_CHECK_=0 ./valgrind_test_4 $ MALLOC_CHECK_=1 ./valgrind_test_4 *** glibc detected *** ./valgrind_test_4: free(): invalid pointer: 0x0804a008 *** $ MALLOC_CHECK_=2 ./valgrind_test_4 Aborted $ MALLOC_CHECK_=3 ./valgrind_test_4 *** glibc detected *** ./valgrind_test_4: free(): invalid pointer: 0x0804a008 *** ======= Backtrace: ========= /lib/i686/cmov/libc.so.6(+0x6b281)[0xb7e24281] /lib/i686/cmov/libc.so.6(cfree+0xd6)[0xb7e28c26] ./valgrind_test_4[0x8048464] ./valgrind_test_4[0x80484ad] /lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7dcfc76] ./valgrind_test_4[0x80483a1] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 08:01 376139 /tmp/valgrind_test_4 08049000-0804a000 rw-p 00000000 08:01 376139 /tmp/valgrind_test_4 0804a000-0806b000 rw-p 0804a000 00:00 0 [heap] b7d9a000-b7db7000 r-xp 00000000 08:01 1016913 /lib/libgcc_s.so.1 b7db7000-b7db8000 rw-p 0001c000 08:01 1016913 /lib/libgcc_s.so.1 b7db8000-b7db9000 rw-p b7db8000 00:00 0 b7db9000-b7ef9000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7ef9000-b7efb000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7efb000-b7efc000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7efc000-b7eff000 rw-p b7efc000 00:00 0 b7f10000-b7f12000 rw-p b7f10000 00:00 0 b7f12000-b7f13000 r-xp b7f12000 00:00 0 [vdso] b7f13000-b7f2e000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7f2e000-b7f2f000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7f2f000-b7f30000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bf9fe000-bfa13000 rw-p bf9fe000 00:00 0 [stack] Aborted $ MALLOC_CHECK_=5 ./valgrind_test_4 free(): invalid pointer $ MALLOC_CHECK_=7 ./valgrind_test_4 free(): invalid pointer ======= Backtrace: ========= /lib/i686/cmov/libc.so.6(+0x6b2a1)[0xb7e402a1] /lib/i686/cmov/libc.so.6(cfree+0xd6)[0xb7e44c26] ./valgrind_test_4[0x8048464] ./valgrind_test_4[0x80484ad] /lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7debc76] ./valgrind_test_4[0x80483a1] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 08:01 376139 /tmp/valgrind_test_4 08049000-0804a000 rw-p 00000000 08:01 376139 /tmp/valgrind_test_4 0804a000-0806b000 rw-p 0804a000 00:00 0 [heap] b7db6000-b7dd3000 r-xp 00000000 08:01 1016913 /lib/libgcc_s.so.1 b7dd3000-b7dd4000 rw-p 0001c000 08:01 1016913 /lib/libgcc_s.so.1 b7dd4000-b7dd5000 rw-p b7dd4000 00:00 0 b7dd5000-b7f15000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f15000-b7f17000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f17000-b7f18000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f18000-b7f1b000 rw-p b7f18000 00:00 0 b7f2c000-b7f2e000 rw-p b7f2c000 00:00 0 b7f2e000-b7f2f000 r-xp b7f2e000 00:00 0 [vdso] b7f2f000-b7f4a000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7f4a000-b7f4b000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7f4b000-b7f4c000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bff59000-bff6e000 rw-p bff59000 00:00 0 [stack] Aborted Valgrind输出的调用栈回溯包含符号信息,"MALLOC_CHECK_=3"输出的调用栈回溯没 有符号信息,不过用IDA反汇编valgrind_test_4,查看0x8048464,正是位于第二个 free()之后。 可以结合GDB、MALLOC_CHECK_调试这类错误: $ gdb -q ./valgrind_test_4 Reading symbols from /tmp/valgrind_test_4...done. (gdb) set environment MALLOC_CHECK_=3 (gdb) run Starting program: /tmp/valgrind_test_4 *** glibc detected *** /tmp/valgrind_test_4: free(): invalid pointer: 0x0804a008 *** ======= Backtrace: ========= /lib/i686/cmov/libc.so.6(+0x6b281)[0xb7ef6281] /lib/i686/cmov/libc.so.6(cfree+0xd6)[0xb7efac26] /tmp/valgrind_test_4[0x8048464] /tmp/valgrind_test_4[0x80484ad] /lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7ea1c76] /tmp/valgrind_test_4[0x80483a1] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 08:01 376139 /tmp/valgrind_test_4 08049000-0804a000 rw-p 00000000 08:01 376139 /tmp/valgrind_test_4 0804a000-0806b000 rw-p 0804a000 00:00 0 [heap] b7e6c000-b7e89000 r-xp 00000000 08:01 1016913 /lib/libgcc_s.so.1 b7e89000-b7e8a000 rw-p 0001c000 08:01 1016913 /lib/libgcc_s.so.1 b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] Program received signal SIGABRT, Aborted. 0xb7fe4410 in ?? () (gdb) bt #0 0xb7fe4410 in ?? () #1 0xb7eb8b82 in abort () from /lib/i686/cmov/libc.so.6 #2 0xb7eec18d in ?? () from /lib/i686/cmov/libc.so.6 #3 0xb7ef6281 in ?? () from /lib/i686/cmov/libc.so.6 #4 0xb7efac26 in free () from /lib/i686/cmov/libc.so.6 #5 0x08048464 in foo (size=1024) at valgrind_test_4.c:17 #6 0x080484ad in main (argc=1, argv=0xbffffb84) at valgrind_test_4.c:30 (gdb) list 17 12 { 13 free( buf ); 14 /* 15 * 错误!二次释放 16 */ 17 free( buf ); 18 } 19 return; 20 } /* end of foo */ 21 (gdb) info symbol 0x8048464 foo + 48 in section .text of /tmp/valgrind_test_4 (gdb) disassemble foo Dump of assembler code for function foo: 0x08048434 : push %ebp 0x08048435 : mov %esp,%ebp 0x08048437 : sub $0x28,%esp 0x0804843a : mov 0x8(%ebp),%eax 0x0804843d : mov %eax,(%esp) 0x08048440 : call 0x8048364 0x08048445 : mov %eax,-0xc(%ebp) 0x08048448 : cmpl $0x0,-0xc(%ebp) 0x0804844c : je 0x8048464 0x0804844e : mov -0xc(%ebp),%eax 0x08048451 : mov %eax,(%esp) 0x08048454 : call 0x8048344 0x08048459 : mov -0xc(%ebp),%eax 0x0804845c : mov %eax,(%esp) 0x0804845f : call 0x8048344 0x08048464 : leave 0x08048465 : ret End of assembler dump. (gdb) 有一件比较诡异的事,直接执行valgrind_test_4时就好像已经设置了MALLOC_CHECK_ 一样,但又不完全一样,主要区别在第一行诊断信息,这次直接说有"double free"。 用Beyond Compare比较这次的输出与"MALLOC_CHECK_=3 ./valgrind_test_4"的输出, 除了第一行诊断信息不一样,另一个比较明显的区别是这次的内存布局中[heap]后面 多了两块区域。实在搞不懂,当前glibc版本是2.11.2-10。 $ ./valgrind_test_4 *** glibc detected *** ./valgrind_test_4: double free or corruption (top): 0x0804a008 *** ======= Backtrace: ========= /lib/i686/cmov/libc.so.6(+0x6b281)[0xb7e22281] /lib/i686/cmov/libc.so.6(+0x6cad8)[0xb7e23ad8] /lib/i686/cmov/libc.so.6(cfree+0x6d)[0xb7e26bbd] ./valgrind_test_4[0x8048464] ./valgrind_test_4[0x80484ad] /lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7dcdc76] ./valgrind_test_4[0x80483a1] ======= Memory map: ======== 08048000-08049000 r-xp 00000000 08:01 376139 /tmp/valgrind_test_4 08049000-0804a000 rw-p 00000000 08:01 376139 /tmp/valgrind_test_4 0804a000-0806b000 rw-p 0804a000 00:00 0 [heap] b7c00000-b7c21000 rw-p b7c00000 00:00 0 b7c21000-b7d00000 ---p b7c21000 00:00 0 b7d98000-b7db5000 r-xp 00000000 08:01 1016913 /lib/libgcc_s.so.1 b7db5000-b7db6000 rw-p 0001c000 08:01 1016913 /lib/libgcc_s.so.1 b7db6000-b7db7000 rw-p b7db6000 00:00 0 b7db7000-b7ef7000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7ef7000-b7ef9000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7ef9000-b7efa000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7efa000-b7efd000 rw-p b7efa000 00:00 0 b7f0e000-b7f10000 rw-p b7f0e000 00:00 0 b7f10000-b7f11000 r-xp b7f10000 00:00 0 [vdso] b7f11000-b7f2c000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7f2c000-b7f2d000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7f2d000-b7f2e000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bfc60000-bfc75000 rw-p bfc60000 00:00 0 [stack] Aborted -------------------------------------------------------------------------- /* * gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_5 valgrind_test_5.c */ #include static void foo ( unsigned int size ) { unsigned char *buf; unsigned int i, j, k; buf = ( unsigned char * )malloc( size ); if ( buf ) { /* * 错误!单字节溢出(off-by-one) */ buf[size] = 0x74; } for ( i = 0; i < 1024; i++ ) { j = i % 2; k = i * 2; } if ( buf ) { free( buf ); } return; } /* end of foo */ int main ( int argc, char * argv[] ) { unsigned int size = 1024; if ( argc > 1 ) { size = ( unsigned int )strtoul( argv[1], NULL, 0 ); } foo( size ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.4 -Wall -pipe -O0 -g -o valgrind_test_5 valgrind_test_5.c $ gdb -q ./valgrind_test_5 Reading symbols from /tmp/valgrind_test_5...done. (gdb) set environment MALLOC_CHECK_=2 (gdb) run Starting program: /tmp/valgrind_test_5 Program received signal SIGABRT, Aborted. 0xb7fe4410 in ?? () (gdb) bt #0 0xb7fe4410 in ?? () #1 0xb7eb8b82 in abort () from /lib/i686/cmov/libc.so.6 #2 0xb7ef62ab in ?? () from /lib/i686/cmov/libc.so.6 #3 0xb7efac26 in free () from /lib/i686/cmov/libc.so.6 #4 0x08048492 in foo (size=1024) at valgrind_test_5.c:26 #5 0x080484db in main (argc=1, argv=0xbffffa04) at valgrind_test_5.c:39 (gdb) list 26 21 j = i % 2; 22 k = i * 2; 23 } 24 if ( buf ) 25 { 26 free( buf ); 27 } 28 return; 29 } /* end of foo */ 30 (gdb) "MALLOC_CHECK_=2"未能直接检查出单字节溢出(off-by-one),它是在free()时发现 堆管理结构有问题。相比之下Valgrind要直白得多: $ valgrind --leak-check=full --show-reachable=yes ./valgrind_test_5 ==16786== Memcheck, a memory error detector ==16786== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==16786== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==16786== Command: ./valgrind_test_5 ==16786== ==16786== Invalid write of size 1 ==16786== at 0x8048457: foo (valgrind_test_5.c:17) ==16786== by 0x80484DA: main (valgrind_test_5.c:39) ==16786== Address 0x4184428 is 0 bytes after a block of size 1,024 alloc'd ==16786== at 0x40255BC: malloc (vg_replace_malloc.c:270) ==16786== by 0x8048444: foo (valgrind_test_5.c:11) ==16786== by 0x80484DA: main (valgrind_test_5.c:39) ==16786== ==16786== ==16786== HEAP SUMMARY: ==16786== in use at exit: 0 bytes in 0 blocks ==16786== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated ==16786== ==16786== All heap blocks were freed -- no leaks are possible ==16786== ==16786== For counts of detected and suppressed errors, rerun with: -v ==16786== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 11 from 6) 总的来说,MALLOC_CHECK_在没有Valgrind时可以应急,好处是不用额外安装什么, 现在这些新版本的Linux应该缺省就支持,但确实不是专业手段。 2.37 用mtrace检查内存错误 A: clan 2007-07-20 09:32 查看mtrace(1)、mtrace(3)。 -------------------------------------------------------------------------- /* * gcc-4.4 -DUSE_MTRACE -Wall -pipe -O0 -g -o valgrind_test_6 valgrind_test_6.c * gcc-4.4 -Wall -pipe -O0 -g -fmudflap -o valgrind_test_6_mudflap valgrind_test_6.c -lmudflap */ #include #ifdef USE_MTRACE #include #endif static void foo ( unsigned int size ) { unsigned char *buf; unsigned int i, j, k; buf = ( unsigned char * )malloc( size ); if ( buf ) { /* * 错误!单字节溢出(off-by-one) */ buf[size] = 0x74; } for ( i = 0; i < 1024; i++ ) { j = i % 2; k = i * 2; } if ( buf ) { free( buf ); } return; } /* end of foo */ static void bar ( unsigned int size ) { void *buf_a, *buf_b; buf_a = malloc( size ); /* * 没有相应的释放buf_b的代码 */ buf_b = malloc( size ); if ( buf_a ) { free( buf_a ); } return; } /* end of bar */ int main ( int argc, char * argv[] ) { unsigned int size = 1024; #ifdef USE_MTRACE mtrace(); #endif if ( argc > 1 ) { size = ( unsigned int )strtoul( argv[1], NULL, 0 ); } foo( size ); bar( size ); #ifdef USE_MTRACE muntrace(); #endif return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.4 -DUSE_MTRACE -Wall -pipe -O0 -g -o valgrind_test_6 valgrind_test_6.c $ MALLOC_TRACE=/tmp/mtrace.log ./valgrind_test_6 $ cat /tmp/mtrace.log = Start @ ./valgrind_test_6:[0x80484a5] + 0x804a378 0x400 @ ./valgrind_test_6:[0x80484f2] - 0x804a378 @ ./valgrind_test_6:[0x8048505] + 0x804a378 0x400 @ ./valgrind_test_6:[0x8048513] + 0x804a780 0x400 @ ./valgrind_test_6:[0x8048527] - 0x804a378 = End $ mtrace ./valgrind_test_6 /tmp/mtrace.log Memory not freed: ----------------- Address Size Caller 0x0804a780 0x400 at /tmp/valgrind_test_6.c:44 $ 用法是,在C代码中嵌入mtrace(),在此之后的malloc(3)、realloc(3)、memalign(3)、 free(3)等函数被hook,当环境变量MALLOC_TRACE指向一个可写日志文件时,这些函 数会向日志写入调试诊断信息;尽管日志本身是文本格式,但不是用户友好的,有一 个Perl脚本mtrace,可以将日志内容转换成用户友好的形式。 MALLOC_TRACE指定的日志文件若已存在,会被截断成0字节后再写入。如果没有设置 环境变量MALLOC_TRACE,或者指定的日志文件对于当前用户来说不可写,mtrace()不 会安装hook。对于SUID/SGID程序,MALLOC_TRACE环境变量被忽略,mtrace机制无效; 我没试/etc/suid-debug对此是否有影响。 muntrace()将去除mtrace()安装的hook,一般不需要调用。 用mtrace只检查出valgrind_test_6.c中的内存泄漏,未查出单字节溢出,Valgrind 显然更猛: $ valgrind --leak-check=full --show-reachable=yes ./valgrind_test_6 ==17764== Memcheck, a memory error detector ==17764== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==17764== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==17764== Command: ./valgrind_test_6 ==17764== ==17764== Invalid write of size 1 ==17764== at 0x80484B7: foo (valgrind_test_6.c:21) ==17764== by 0x8048574: main (valgrind_test_6.c:65) ==17764== Address 0x4184428 is 0 bytes after a block of size 1,024 alloc'd ==17764== at 0x40255BC: malloc (vg_replace_malloc.c:270) ==17764== by 0x80484A4: foo (valgrind_test_6.c:15) ==17764== by 0x8048574: main (valgrind_test_6.c:65) ==17764== ==17764== ==17764== HEAP SUMMARY: ==17764== in use at exit: 1,024 bytes in 1 blocks ==17764== total heap usage: 3 allocs, 2 frees, 3,072 bytes allocated ==17764== ==17764== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==17764== at 0x40255BC: malloc (vg_replace_malloc.c:270) ==17764== by 0x8048512: bar (valgrind_test_6.c:44) ==17764== by 0x8048580: main (valgrind_test_6.c:66) ==17764== ==17764== LEAK SUMMARY: ==17764== definitely lost: 1,024 bytes in 1 blocks ==17764== indirectly lost: 0 bytes in 0 blocks ==17764== possibly lost: 0 bytes in 0 blocks ==17764== still reachable: 0 bytes in 0 blocks ==17764== suppressed: 0 bytes in 0 blocks ==17764== ==17764== For counts of detected and suppressed errors, rerun with: -v ==17764== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 11 from 6) 2.38 GDB中如何调试signal handler Q: 有些signal我想让GDB接管、处理之后不交给被调试进程,而另一些signal我不想让 GDB接管而是直接传递给被调试进程。总之,类似这些与signal handler相关的调试, 如何做? A: scz 2013-11-04 12:45 GDB有个handle命令: handle -------------------------------------------------------------------------- 指定信号,推荐使用形如SIGSEGV这样的表达方式。为了和老版本的GDB兼容,也 支持[1,15]区间上的数字。用数字指定信号时,可以用形如1-5这样表达方式同 时指定5个信号。有一个特殊值"all",对应除去被GDB本身所用信号之外的其他 所有信号;GDB典型地使用SIGTRAP、SIGINT这些信号。 $ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX stop 捕捉到信号后将控制权交给GDB,隐含print nostop print 捕捉到信号后显示一条提示信息 noprint pass 将信号传递给被调试进程 nopass 被调试进程看不到指定信号 ignore 同nopass noignore 同pass pass与stop可以组合使用,不互斥。 -------------------------------------------------------------------------- "handle SIGSEGV stop nopass"表示GDB自己处理SIGSEGV,被调试进程看不到该信号。 有一些相关命令: -------------------------------------------------------------------------- info signals 查看各个信号的 info signals SIGSEGV 查看SIGSEGV的 signal 向被调试进程发送指定信号并continue signal 0 不向被调试进程传递信号并continue,应该就是nopass的效果 -------------------------------------------------------------------------- 2.39 手工编译安装新版本的GDB Q: # dpkg -S `which gdb` gdb: /usr/bin/gdb # dpkg -L gdb /. /etc /etc/gdb /etc/gdb/gdbinit /usr /usr/bin /usr/bin/gcore /usr/bin/gdb /usr/bin/gdbtui /usr/share /usr/share/doc /usr/share/doc/gdb ... /usr/share/menu /usr/share/menu/gdb /usr/share/gdb ... /usr/share/man /usr/share/man/man1 /usr/share/man/man1/gdb.1.gz /usr/share/man/man1/gdbtui.1.gz /usr/share/man/man1/gcore.1.gz # gdb --version GNU gdb (GDB) 7.4.1-debian Copyright (C) 2012 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". For bug reporting instructions, please see: . 这个GDB版本太低了,7.5.1之前的GDB在加载/lib/ld-linux.so.2的调试符号信息时 存在BUG,我需要升级GDB。 参看: gdb: Debugging symbols of ld.so not found - [2012-07-04] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=680173 # wget http://ftp.debian.org/debian/pool/main/g/gdb/gdb_7.6.1-1_i386.deb # dpkg -i gdb_7.6.1-1_i386.deb 报错,说这个版本的GDB依赖2.15版的glibc,系统中当前glibc是2.13版。 A: scz@nsfocus 2013-11-12 13:37 那就手工编译安装新版本的GDB吧。 wget http://ftp.gnu.org/gnu/gdb/gdb-7.6.1.tar.bz2 tar xvfj gdb-7.6.1.tar.bz2 cd gdb-7.6.1/ rm -rf build mkdir build cd build ../configure --help ../configure --program-transform-name='s,\(.*\),scz-\1-7.6.1,' -v make -j 6 make install --target不能随便指定,参看: gdb-7.6.1/bfd/config.bfd # ls -l /usr/local/bin/scz-gdb* -rwxr-xr-x 1 root staff 23110172 Nov 12 12:55 /usr/local/bin/scz-gdb-7.6.1* -rwxr-xr-x 1 root staff 987881 Nov 12 12:55 /usr/local/bin/scz-gdbserver-7.6.1* # ls -l /usr/local/man/man1/scz-gdb* -rw-r--r-- 1 root staff 8625 Nov 12 12:55 /usr/local/man/man1/scz-gdb-7.6.1.1 -rw-r--r-- 1 root staff 4575 Nov 12 12:55 /usr/local/man/man1/scz-gdbserver-7.6.1.1 # scz-gdb-7.6.1 --version GNU gdb (GDB) 7.6.1 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-pc-linux-gnu". For bug reporting instructions, please see: . 修改printcmd.c,以增加"x/64bm"、"x/64hm"、"x/64wm"、"x/64gm"这种内存转储显 示。 2.40 源码级调试GLIBC Q: 我的glibc是系统自带的,不是用源码编译安装的,现在我想对glibc进行源码级调试, 或者说我想跟踪到glibc的函数内部去。 A: zyh & scz 2013-11-11 14:30 1) aptitude install libc6-dbg 2) apt-get source libc6:i386 3) 使用7.5.1版及之后的gdb 4) set debug-file-directory /usr/lib/debug:/usr/local/lib/debug 5) set directories $cdir:$cwd:/usr/src/glibc/eglibc-2.13/... 6) set verbose on 第4、5步的目录根据实际情况修改,第6步不必要,只是方便观察。 D: zyh 2013-11-14 11:00 第5步还可以优化: (gdb) help directory Add directory DIR to beginning of search path for source files. Forget cached info on source file locations and line positions. DIR can also be $cwd for the current working directory, or $cdir for the directory in which the source file was compiled into object code. With no argument, reset the search path to $cdir:$cwd, the default. (gdb) "directory "会在原有搜索路径最前面添加。 2.41 -g与-ggdb的区别 Q: 有的编译选项中同时出现"-g -ggdb",有的只有"-g",二者有何区别? A: Ian Lance Taylor 2012-05-01 参看: http://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html -------------------------------------------------------------------------- -g 以操作系统native格式产生调试信息,gdb可以使用这些调试信息。所谓native 格式可能是stabs、COFF、XCOFF、DWARF 2等格式之一,绝大多数系统上native 格式对应stabs格式。 -g会产生一些只有gdb能用的额外的调试信息,使得gdb调试更强大,但其他调试 器不认这些额外的调试信息,甚至无法调试包含这种额外调试信息的程序。你可 以用-gstabs、-gstabs+、-gxcoff、-gxcoff+等等精确控制是否产生这种额外的 调试信息。 -g可以和-O一起使用。 -ggdb 产生供gdb使用的调试信息,这意味着使用最具表现力的格式,优先使用DWARF 2 格式,其次使用stabs格式,前两种都不被支持的情况下使用native格式。尽可 能产生只有gdb能用的额外的调试信息。 -g[level] -ggdb[level] 通过level控制调试信息的丰富程度,缺省level为2。 0 不产生调试信息 1 较少的调试信息,可以bt,包含函数、全局变量的信息,不包含局部变量和 行号信息 3 最多的调试信息,包含宏定义,支持宏扩展 -------------------------------------------------------------------------- 参看: Concrete difference between -g and -ggdb - Ian Lance Taylor [2012-05-01] http://gcc.gnu.org/ml/gcc-help/2012-05/msg00003.html 不幸的是,上述gcc文档需要更新了。当前绝大多数系统上native格式对应DWARF 2格 式。-ggdb相比-gstabs+有很多增强,记录const/volatile限定词,记录源文件的路 径信息而不仅仅是源文件名,支持非标准尺寸的整型,支持布尔型等等。值得注意的 是,在绝大多数系统(包括所有的GNU/Linux系统)上-g缺省等价于-ggdb。 现在就只指定-g好了,如果有需求,可以-g3。 2.42 Debian latrace(1) A: scz 2013-12-02 11:18 参看"16.26 LD_AUDIT的使用"以及latrace(1)。 -------------------------------------------------------------------------- NAME latrace - LD_AUDIT审计库前端工具 SYNOPSIS latrace [-snltfvhiBdISbcCyYLpoaNADVTFERq] command [arg ...] DESCRIPTION latrace利用LD_AUDIT跟踪库函数调用。 latrace用法类似strace(1)、ltrace(1)。 OPTIONS -l, --libs lib1[,lib2,...] 指定监控对象,同时指定LA_FLG_BINDTO、LA_FLG_BINDFROM。 $ latrace -l /lib/i386-linux-gnu/i686/cmov/libc.so.6 /bin/true 5752 __libc_start_main [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5752 exit [/lib/i386-linux-gnu/i686/cmov/libc.so.6] -t, --libs-to lib1[,lib2,...] 类似-l,但只指定了LA_FLG_BINDTO。 $ latrace -t /lib/i386-linux-gnu/i686/cmov/libc.so.6 /bin/true 5754 __libc_start_main [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5754 exit [/lib/i386-linux-gnu/i686/cmov/libc.so.6] -f, --libs-from lib1[,lib2,...] 类似-l,但只指定了LA_FLG_BINDFROM。这个一般无输出。 -s, --sym sym1[,sym2,...] 监视指定符号 $ latrace -s exit -A /bin/true 5762 exit(status = 0) [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { -n, --sym-omit sym1[,sym2,...] 忽略指定符号 $ latrace -n exit /bin/true 5772 __libc_start_main [/lib/i386-linux-gnu/i686/cmov/libc.so.6] -L, --lib-subst s1[,s2,...] 让la_objsearch()返回非NULL、非name的值,以此更改被加载的共享库。 sN形如"src [=%~] dst" = 比较library name与src,如果相等,将src换成dst library name - /lib/krava1.so src - /lib/krava1.so dst - /lib/krava2.so final library name - /lib/krava2.so % 在library name中搜索子串src,如果找到,将src换成dst library name - /lib/krava1.so src - krava1 dst - krava2 final library name - /lib/krava2.so ~ 在library name中搜索子串src,如果找到,将library name换成dst library name - /lib/krava1.so src - krava1 dst - /usr/lib/krava3.so final library name - /usr/lib/krava3.so -c, --counts 给出库函数调用的次数、消耗时间等摘要信息 $ latrace -c /bin/true ... -------------------------------------------------------------------------- Statistics for [/bin/true] total runtime: 0.002472 sec Thread 5800 (runtime 0.000595 sec) sec usec % usec/call calls symbol --- ------ ---------- ---------- ---------- ------------------------------ 0:000000 -nan 0 1 __libc_start_main@/lib/i386-linux-gnu/i686/cmov/libc.so.6 0:000000 -nan 0 1 exit@/lib/i386-linux-gnu/i686/cmov/libc.so.6 -C, --sort-counts 隐含-c,指定排序字段,可取值有time、per、ucall、call、sym、lib,缺 省按call排序(降序显示)。 $ latrace -C sym /usr/bin/id -p, --pipe 使用管道模式,此时latrace不使用被跟踪进程的stdout。 -N, --conf 指定配置文件,功能同命令行参数 -A, --enable-args 显示函数参数 $ latrace -A /bin/true 5514 __libc_start_main(main = 0x8048d60, argc = 1, ubp_av = 0xbfdd02a4, auxvec = 0x804b2e0, init = 0x804b2d0, fini = 0xb77c9590, rtld_fini = 0xbfdd029c) [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 5514 exit(status = 0) [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 在没有调试符号的情况下,这倒是个快速定位main()函数的办法。 -D, --detail-args 与-A一起使用,如果形参类型是结构,显示结构成员。 -a, --args 指定函数参数定义文件,隐式包含-A。 缺省是"/etc/latrace.d/headers/latrace.h" 对于可变参数函数(比如printf),定义函数原型时只能包含固定参数,比如: $ find /etc/latrace.d/headers -name "*.h" -exec grep printf {} \; int fprintf(FILE *stream, char *format); int printf(char *format); int sprintf(void *s, char *format); -y, --framesize framesize for storing the stack before pltexit (default 100) -Y, --no-framesize-check disable framesize check -F, --no-follow-fork 不要跟踪fork()产生的子进程 -E, --no-follow-exec 不要跟踪exec*() -S, --timestamp 显示时间戳 $ latrace -S /bin/true [11/02/2013 11:54:06.163138] 5720 __libc_start_main [/lib/i386-linux-gnu/i686/cmov/libc.so.6] [11/02/2013 11:54:06.163310] 5720 exit [/lib/i386-linux-gnu/i686/cmov/libc.so.6] -b, --flow-below sym1[,sym2,...] 以指定函数为根显示调用关系(靠缩进识别主调、被调) $ latrace -b fopen,getgrgid /usr/bin/id 5730 fopen [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 getgrgid [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 __nss_database_lookup [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] ... 5730 fopen [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fgetpos [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fgets_unlocked [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 __ctype_b_loc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 _nss_files_parse_grent [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fclose [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 free [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 getgrgid [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fopen [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fgetpos [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fgets_unlocked [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 __ctype_b_loc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 _nss_files_parse_grent [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 fclose [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5730 free [/lib/i386-linux-gnu/i686/cmov/libc.so.6] -I, --no-indent-sym 取消缩进。这是什么脑残参数啊,取消缩进后无法识别主调、被调。 -i, --indent-sym 指定缩进尺寸 $ latrace -b fopen -i 4 /usr/bin/id 5844 fopen [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5844 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5844 fopen [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5844 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5844 fopen [/lib/i386-linux-gnu/i686/cmov/libc.so.6] 5844 malloc [/lib/i386-linux-gnu/i686/cmov/libc.so.6] -B, --braces allways display {} for the around the symbol body -d, --demangle 让C++函数名更可读 -T, --hide-tid 不要显示tid -o, --output 输出到指定文件 -R, --ctl-config controled config feature -q, --disable run with disabled auditing 这种参数都不知道拿来干什么 -v, --verbose 冗余模式 -V, --version 显示版本 -h, --help 显示帮助 EXAMPLES 最简使用方式 latrace cat 查看库函数参数 latrace -A cat 指定函数参数定义文件 latrace -a latrace.conf cat 只跟踪read()、write() latrace -A -s read,write cat 以sysconf()为根查看调用关系 latrace -b sysconf kill `pidof sleep` 只监控libproc中定义的库函数 $ latrace -c -l /lib/i386-linux-gnu/libprocps.so.0 w ... -------------------------------------------------------------------------- Statistics for [w] total runtime: 0.010178 sec Thread 5933 (runtime 0.007402 sec) sec usec % usec/call calls symbol --- ------ ---------- ---------- ---------- ------------------------------ 0:001351 14.914992 16 80 user_from_uid@/lib/i386-linux-gnu/libprocps.so.0 0:000132 1.457275 22 6 escape_str@/lib/i386-linux-gnu/libprocps.so.0 0:000078 0.861117 39 2 tty_to_dev@/lib/i386-linux-gnu/libprocps.so.0 0:000403 4.449106 201 2 escape_command@/lib/i386-linux-gnu/libprocps.so.0 0:000311 3.433429 155 2 escape_strlist@/lib/i386-linux-gnu/libprocps.so.0 0:000578 6.381100 578 1 cpuinfo@/lib/i386-linux-gnu/libprocps.so.0 0:004848 53.521748 4848 1 readproctab@/lib/i386-linux-gnu/libprocps.so.0 0:000041 0.452639 41 1 openproc@/lib/i386-linux-gnu/libprocps.so.0 0:000006 0.066240 6 1 closeproc@/lib/i386-linux-gnu/libprocps.so.0 0:000959 10.587326 959 1 print_uptime@/lib/i386-linux-gnu/libprocps.so.0 0:000282 3.113270 282 1 sprint_uptime@/lib/i386-linux-gnu/libprocps.so.0 0:000046 0.507838 46 1 uptime@/lib/i386-linux-gnu/libprocps.so.0 0:000023 0.253919 23 1 loadavg@/lib/i386-linux-gnu/libprocps.so.0 给出库函数调用的次数、消耗时间等摘要信息 latrace -c ls 给出库函数调用的次数、消耗时间等摘要信息,按time排序 latrace -C time ls 输出到指定文件 latrace -o latrace.logk ls 程序test依赖libkrava1.so,修改成依赖libkrava2.so latrace -L krava1%krava2 test latrace -L krava1~libkrava2.so test latrace -L libkrava1.so=libkrava2.so test DISCUSSION 对于"-l -t -f -s -n -b",支持*号,表示通配。 -s "*krava" 匹配以"krava"结尾的串 -s "krava" 严格匹配"krava" latrace使用的审计库是: /usr/lib/latrace/libltaudit.so.0.5.11 $ nm -D /usr/lib/latrace/libltaudit.so.0.5.11 | grep la_ 00003390 T la_activity 000036f0 T la_i86_gnu_pltenter 000037a0 T la_i86_gnu_pltexit 00003530 T la_objclose 000032d0 T la_objopen 00003440 T la_objsearch 00003480 T la_preinit 000035e0 T la_symbind32 000032c0 T la_version 但不能这样用: $ LD_AUDIT=/usr/lib/latrace/libltaudit.so.0.5.11 /bin/true open failed: No such file or directory Segmentation fault # strace -o strace.log -E LD_AUDIT=/usr/lib/latrace/libltaudit.so.0.5.11 /bin/true 查看strace.log了解更多信息。 latrace的大部分参数既可以在命令行上指定,也可以在配置文件中指定,少量 参数只能在配置文件中指定。缺省配置文件是: /etc/latrace.d/latrace.conf 命令行参数会覆盖配置文件中的设置。 配置文件的语法: INCLUDE OPTIONS { OPTION1 = VALUE OPTION2 = YES|NO ... OPTIONN = VALUE } # comment 可用配置选项: HEADERS = FILE -a, --args 缺省是"/etc/latrace.d/headers/latrace.h" INDENT_SYM = VALUE -i, --indent-sym 缺省是2 PIPE = BOOL -p, --pipe 缺省是YES TIMESTAMP = BOOL -S, --timestamp 缺省是NO FRAMESIZE = VALUE -y, --framesize FRAMESIZE_CHECK = BOOL -Y, --no-framesize-check HIDE_TID = BOOL -T, --hide-tid 缺省是NO FOLLOW_FORK = BOOL -F, --no-follow-fork 缺省是YES FOLLOW_EXEC = BOOL -E, --no-follow-exec 缺省是YES DEMANGLE = BOOL -d, --demangle 缺省是NO BRACES = BOOL -B, --braces 缺省是NO ENABLE_ARGS = BOOL -A, --enable-args 缺省是NO DETAIL_ARGS = BOOL -D, --detail-args 缺省是NO OUTPUT_TTY = FILE 没有命令行参数与之对应。缺省没有定义。 -o是将latrace的输出转向到指定文件,OUTPUT_TTY则是将被跟踪进程的输 出转向到指定文件,可以指定成"/dev/pts/1"这种文件。 LIBS = LIB1[,LIB2,...] -l, --libs LIBS_TO = LIB1[,LIB2,...] -t, --libs-to LIBS_FROM = LIB1[,LIB2,...] -f, --libs-from SYM = SYM1[,SYM2,...] -s, --sym SYM_OMIT = SYM1[,SYM2,...] -n, --sym-omit SYM_BELOW = SYM1[,SYM2,...] -b, --flow-below SYM_NOEXIT = SYM1[,SYM2,...] 没有命令行参数与之对应。缺省指定"_setjmp"。 针对指定符号不调用la_pltexit()。 ARGS_STRING_POINTER_LENGTH = BOOL 没有命令行参数与之对应。缺省为NO。 显示函数参数时,如果参数是字符串,显示指针本身以及字符串长度。 对比下面两组输出,依次对应NO、YES: $ latrace -AD stat /bin/true | grep fopen 6051 fopen64(filename = "/proc/filesystems", modes = "r") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6051 } fopen64 = 0x805b3e8 6051 fopen(filename = "/etc/passwd", modes = "rme") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6051 } fopen = 0x8065928 6051 fopen(filename = "/etc/default/nss", modes = "rc") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6051 } fopen = 0x8065a90 6051 fopen(filename = "/etc/passwd", modes = "rme") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6051 } fopen = 0x8065ec0 $ latrace -AD stat /bin/true | grep fopen 6055 fopen64(filename = (0xb7d2912d, 17) "/proc/filesystems", modes = (0xb7d28f73, 1) "r") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6055 } fopen64 = 0x805b3e8 6055 fopen(filename = (0xb7fb0968, 11) "/etc/passwd", modes = (0xb7fb0918, 3) "rme") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6055 } fopen = 0x8065928 6055 fopen(filename = (0xb7a7caf1, 16) "/etc/default/nss", modes = (0xb7a7b147, 2) "rc") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6055 } fopen = 0x8065a90 6055 fopen(filename = (0xb7fb0968, 11) "/etc/passwd", modes = (0xb7fb0918, 3) "rme") [/lib/i386-linux-gnu/i686/cmov/libc.so.6] { 6055 } fopen = 0x8065ec0 SEE ALSO strace(1), ltrace(1) -------------------------------------------------------------------------- 2.43 "b main"与"b *main"有什么区别 A: # gdb -q ./sigaltstack_test (gdb) start Temporary breakpoint 1 at 0x8049591: file sigaltstack_test.c, line 866. Temporary breakpoint 1, main (argc=1, argv=0xbffffcb4) at sigaltstack_test.c:866 866 void * base[3] = { NULL }; (gdb) b main Breakpoint 2 at 0x8049591: file sigaltstack_test.c, line 866. (gdb) b *main Breakpoint 3 at 0x8049582: file sigaltstack_test.c, line 861. (gdb) x/10i main 0x8049582
: push ebp 0x8049583 : mov ebp,esp 0x8049585 : push edi 0x8049586 : push esi 0x8049587 : push ebx 0x8049588 : and esp,0xfffffff0 0x804958b : sub esp,0xe0 => 0x8049591 : mov DWORD PTR [esp+0x38],0x0 0x8049599 : mov DWORD PTR [esp+0x3c],0x0 0x80495a1 : mov DWORD PTR [esp+0x40],0x0 (gdb) "b *main"将在main()的第一条汇编指令处(0x8049582)设置断点,此时尚未建立栈帧, 无法有效定位形参和局部变量。 "b main"在0x8049591处设置断点,此时栈帧已建立,局部变量的空间已分配。 "start"相当于"tb main"、"run"。 参看: http://www.gnu.org/software/gdb/documentation/ https://sourceware.org/gdb/current/onlinedocs/gdb/ https://sourceware.org/gdb/current/onlinedocs/gdb/Command-and-Variable-Index.html https://sourceware.org/gdb/current/onlinedocs/gdb/Specify-Location.html 2.44 ~/.gdbinit A: scz 2014-02-26 13:59 我在32-bits的x86/Linux上用这个: -------------------------------------------------------------------------- set pagination off set disassembly-flavor intel set print array on set print array-indexes on set print pretty on set print union on set print asm-demangle on set print demangle on set demangle-style auto set debug-file-directory /usr/lib/debug:/usr/local/lib/debug set directories /usr/src/glibc/eglibc-2.13/elf:/usr/src/glibc/eglibc-2.13/nptl -------------------------------------------------------------------------- 2.45 GDB中如何hexdump Q: windbg中可以用db命令进行hexdump,gdb的"x/16bx"达不到同样效果,查看内存时很 不方便。 A: scz 过去我们通过修改gdb源码printcmd.c,增加"x/16bm"命令,以此实现hexdump。 参: "2.31 GDB中如何调用指定动态链接库中的导出函数" 如果被调试进程空间中有dlopen(),可以动态加载helper.so并调用其中的hexdump() 函数。这个方案不能用于分析coredump的情形,此时无法调用dlopen()。 最优方案是写gdb脚本,参看: https://sourceware.org/gdb/onlinedocs/gdb/Define.html -------------------------------------------------------------------------- define hexdump if ( $argc < 1 ) help hexdump else set $addr = $arg0 if ( $argc >= 2 ) set $size = $arg1 else set $size = 64 end if ( $argc >= 3 ) set $count = $arg2 else set $count = 16 end set $_i = 0 set $_offset = 0 set $_m = ( $count + 1 ) / 2 set $_k = $size / $count while ( $_k > 0 ) printf "%08X", $addr + $_offset set $_j = 0 while ( $_j < $count ) if ( $_m == $_j ) printf "-%02X", *( unsigned char * )( $addr + $_i ) else printf " %02X", *( unsigned char * )( $addr + $_i ) end set $_j = $_j + 1 set $_i = $_i + 1 end printf " " set $_i = $_i - $count set $_j = 0 while ( $_j < $count ) if ( ( *( unsigned char * )( $addr + $_i ) >= 0x20 ) && ( *( unsigned char * )( $addr + $_i ) != 0x7F ) && ( *( unsigned char * )( $addr + $_i ) < 0xFF ) ) printf "%c", *( unsigned char * )( $addr + $_i ) else printf "." end set $_j = $_j + 1 set $_i = $_i + 1 end printf "\n" set $_k = $_k - 1 set $_offset = $_offset + $count end set $_k = $size - $_i if ( $_k > 0 ) printf "%08X", $addr + $_offset set $_j = 0 while ( $_j < $_k ) if ( $_m == $_j ) printf "-%02X", *( unsigned char * )( $addr + $_i ) else printf " %02X", *( unsigned char * )( $addr + $_i ) end set $_j = $_j + 1 set $_i = $_i + 1 end set $_i = $_i - $_k set $_j = $count - $_k while ( $_j > 0 ) printf " " set $_j = $_j - 1 end printf " " set $_j = 0 while ( $_j < $_k ) if ( ( *( unsigned char * )( $addr + $_i ) >= 0x20 ) && ( *( unsigned char * )( $addr + $_i ) != 0x7F ) && ( *( unsigned char * )( $addr + $_i ) < 0xFF ) ) printf "%c", *( unsigned char * )( $addr + $_i ) else printf "." end set $_j = $_j + 1 set $_i = $_i + 1 end printf "\n" end end end document hexdump hexdump [size] [count] end -------------------------------------------------------------------------- 起始地址 [size] hexdump的字节数,缺省64 [count] hexdump时单行显示的字节数上限,缺省16 -------------------------------------------------------------------------- 该脚本支持汉字显示。脚本方案不用自己修改gdb源码,调试cisco时可以直接使用。 A: scz 2014-02-27 09:36 我们按windbg的使用习惯实现db、dh、dw命令: -------------------------------------------------------------------------- define db if ( $argc < 1 ) help db else if ( $argc >= 2 ) set $num = $arg1 else set $num = 64 end if ( $argc >= 3 ) set $count = $arg2 else set $count = 16 end dx 'b' $arg0 $num $count end end document db db [num] [count] end define dh if ( $argc < 1 ) help dh else if ( $argc >= 2 ) set $num = $arg1 else set $num = 64 / 2 end if ( $argc >= 3 ) set $count = $arg2 else set $count = 16 end dx 'h' $arg0 $num $count end end document dh dh [num] [count] end define dw if ( $argc < 1 ) help dw else if ( $argc >= 2 ) set $num = $arg1 else set $num = 64 / 4 end if ( $argc >= 3 ) set $count = $arg2 else set $count = 16 end dx 'w' $arg0 $num $count end end document dw dw [num] [count] end define dx if ( $argc < 2 ) help dx else set $type = $arg0 if ( 'b' == $type ) set $_n = 1 end if ( 'h' == $type ) set $_n = 2 end if ( 'w' == $type ) set $_n = 4 end set $addr = $arg1 if ( $argc >= 3 ) set $size = $arg2 * $_n else set $size = 64 end if ( $argc >= 4 ) set $count = ( $arg3 + 15 ) / 16 * 16 else set $count = 16 end set $_i = 0 set $_offset = 0 set $_k = $size / $count while ( $_k > 0 ) printf "%08X", $addr + $_offset set $_j = 0 while ( $_j < $count ) printf " " if ( 'b' == $type ) printf "%02X", *( unsigned char * )( $addr + $_i ) end if ( 'h' == $type ) printf "%04X", *( unsigned short int * )( $addr + $_i ) end if ( 'w' == $type ) printf "%08X", *( unsigned int * )( $addr + $_i ) end set $_j = $_j + $_n set $_i = $_i + $_n end printf " " set $_i = $_i - $count set $_j = 0 while ( $_j < $count ) if ( ( *( unsigned char * )( $addr + $_i ) >= 0x20 ) && ( *( unsigned char * )( $addr + $_i ) != 0x7F ) && ( *( unsigned char * )( $addr + $_i ) < 0xFF ) ) printf "%c", *( unsigned char * )( $addr + $_i ) else printf "." end set $_j = $_j + 1 set $_i = $_i + 1 end printf "\n" set $_k = $_k - 1 set $_offset = $_offset + $count end set $_k = $size - $_i if ( $_k > 0 ) printf "%08X", $addr + $_offset set $_j = 0 while ( $_j < $_k ) printf " " if ( 'b' == $type ) printf "%02X", *( unsigned char * )( $addr + $_i ) end if ( 'h' == $type ) printf "%04X", *( unsigned short int * )( $addr + $_i ) end if ( 'w' == $type ) printf "%08X", *( unsigned int * )( $addr + $_i ) end set $_j = $_j + $_n set $_i = $_i + $_n end set $_i = $_i - $_k set $_j = $count - $_k while ( $_j > 0 ) if ( 'b' == $type ) printf " " end if ( 'h' == $type ) printf " " end if ( 'w' == $type ) printf " " end set $_j = $_j - $_n end printf " " set $_j = 0 while ( $_j < $_k ) if ( ( *( unsigned char * )( $addr + $_i ) >= 0x20 ) && ( *( unsigned char * )( $addr + $_i ) != 0x7F ) && ( *( unsigned char * )( $addr + $_i ) < 0xFF ) ) printf "%c", *( unsigned char * )( $addr + $_i ) else printf "." end set $_j = $_j + 1 set $_i = $_i + 1 end printf "\n" end end end document dx dx [num] [count] end -------------------------------------------------------------------------- 2.46 加载、卸载.so时断下来 Q: windbg可以在模块加载、卸载时设置断点: sxe ld: sxe ud: gdb有无类似能力? A: scz 2014-02-26 16:31 $ scz-gdb-7.6.1 -q /usr/bin/id (gdb) help set stop-on-solib-events Set stopping for shared library events. If nonzero, gdb will give control to the user when the dynamic linker notifies gdb of shared library events. The most common event of interest to the user would be loading/unloading of a new library. (gdb) set stop-on-solib-events 1 (gdb) r Starting program: /usr/bin/id Stopped due to shared library event (no libraries added or removed) (gdb) c Continuing. Stopped due to shared library event: Inferior loaded /lib/i386-linux-gnu/libselinux.so.1 /lib/i386-linux-gnu/i686/cmov/libc.so.6 /lib/i386-linux-gnu/i686/cmov/libdl.so.2 (gdb) info sharedlibrary From To Syms Read Shared Object Library 0xb7fe2820 0xb7ff905f Yes /lib/ld-linux.so.2 0xb7fa6070 0xb7fb7aa8 Yes (*) /lib/i386-linux-gnu/libselinux.so.1 0xb7e54c90 0xb7f6336c Yes /lib/i386-linux-gnu/i686/cmov/libc.so.6 0xb7e39a60 0xb7e3a9a8 Yes /lib/i386-linux-gnu/i686/cmov/libdl.so.2 (*): Shared library is missing debugging information. (gdb) stop-on-solib-events没有"sxe ld"那么强大,无法指定.so。不太清楚哪些事件发生 时gdb会断下来,无法指定事件。与LD_AUDIT审计事件对了一下,不太像。反正加载 .so成功时会断下来。具体实现上好像有BUG,比如示例中第二次断下来时一次性加载 了三个.so。 3. -lelf、-lkvm、-lkstat相关问题 3.1 如何判断可执行文件是否携带了调试信息 Q: 某些时候需要知道编译可执行文件时是否携带了调试信息(比如是否指定了-g编译选 项)。 A: Sun Microsystems 2000-05-15 检查可执行文件中是否包含".stab" elf section,".stab" section用于保存相关调 试信息。 FAQ ID - 3060 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/3060(需要口令) Document ID - 3060 http://sunsolve.sun.com/pub-cgi/retrieve.pl?doc=faqs/3060 http://sunsolve.sun.com/pub-cgi/Redir.pl?doc=faqs/3060 http://sunsolve.sun.com/search/document.do?assetkey=1-30-3060-1 下面这个脚本演示如何判断可执行文件是否携带调试信息 -------------------------------------------------------------------------- #! /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 -------------------------------------------------------------------------- 下面这段C代码同样演示如何判断可执行文件是否携带调试信息: -------------------------------------------------------------------------- /* * gcc -Wall -pipe -g -o checkelf checkelf.c -lelf * Usage: ./checkelf ./checkelf */ #include #include #include #include #include #include int main ( int argc, char * argv[] ) { Elf32_Shdr *shdr; Elf32_Ehdr *ehdr; Elf *elf; Elf_Scn *scn; Elf_Data *data; int fd; unsigned int cnt; if ( 2 != argc ) { fprintf( stderr, "Usage: %s file\n", argv[0] ); return( EXIT_FAILURE ); } /* * Open the input file */ if ( ( fd = open( argv[1], O_RDONLY ) ) <= 0 ) { fprintf( stderr, "Couldn't open %s for reading\n", argv[1] ); return( EXIT_FAILURE ); } ( void )elf_version( EV_CURRENT ); /* * Get the ELF descriptor */ if ( NULL == ( elf = elf_begin( fd, ELF_C_READ, NULL ) ) ) { fprintf( stderr, "%s\n", elf_errmsg( elf_errno() ) ); close( fd ); return( EXIT_FAILURE ); } /* * Find the .shstrtab data buffer */ if ( ( NULL == ( ehdr = elf32_getehdr( elf ) ) ) || /* * elf_getscn() returns a section descriptor, given an index * into the file's section header table * * 返回的scn代表了"section name string table" * * e_shstrndx,两个字节,该成员指明一个section header table中的索 * 引值,对应的表项与setion name string table有关。如果文件没有 * setion name string table,该成员设置成SHN_UNDEF(0)。 */ ( NULL == ( scn = elf_getscn( elf, ehdr->e_shstrndx ) ) ) || ( NULL == ( data = elf_getdata( scn, NULL ) ) ) ) { fprintf( stderr, "%s\n", elf_errmsg( elf_errno() ) ); close( fd ); return( EXIT_FAILURE ); } /* * Here we go through each section header in the file and look for * the .stab section */ for ( cnt = 1, scn = NULL; ( scn = elf_nextscn( elf, scn ) ); cnt++ ) { if ( ( shdr = elf32_getshdr( scn ) ) == NULL ) { fprintf( stderr, "%s\n", elf_errmsg( elf_errno() ) ); close( fd ); return( EXIT_FAILURE ); } /* * sh_name,4字节,该值是一个索引值,用于在section name string table * 中定位一个串(asciiz串),该串即节名。 */ else if ( !( strcmp( ( char * )data->d_buf + shdr->sh_name, ".stab" ) ) ) { fprintf ( stdout, "The file '%s' has been compiled for debug\n", argv[1] ); return( EXIT_SUCCESS ); } } fprintf ( stderr, "The file '%s' has not been compiled for debug\n", argv[1] ); return( EXIT_FAILURE ); } /* end of main */ -------------------------------------------------------------------------- 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: scz@nsfocus 下面是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 -static -Wall -pipe -g -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: scz@nsfocus 下面是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 ( 3 != argc ) { 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 ); */ close( dst ); close( src ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 对照这个使用标准文件I/O接口的实现 -------------------------------------------------------------------------- /* * gcc -static -Wall -pipe -g -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 ( 3 != argc ) { 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( dst ); close( src ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- mmap()好处是处理大文件时速度明显快于标准文件I/O,无论读写,都少了一次用户 空间与内核空间之间的复制过程。操作内存还便于设计、优化算法。 A: scz@nsfocus 下面是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 U夊WVS侅$...j.h? 00000010 8B 04 08 E8 A4 FC FF FF-89 C3 83 C4 10 85 DB 79 ?.瑜?壝兡.呟y offset = 0x804886d byteArray [ 32 bytes ] ----> 00000000 89 E5 57 56 53 81 EC 24-10 00 00 6A 00 68 81 8B 夊WVS侅$...j.h亱 00000010 04 08 E8 A4 FC FF FF 89-C3 83 C4 10 85 DB 79 1B ..瑜?壝兡.呟y. 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: scz@nsfocus 下面是一个利用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@nsfocus 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 main = 0x80487a4 byteArray [ 32 bytes ] ----> 00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 00 68 01 U夊侅....WVSj.h. 00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔兡 offset = 0x8048000 main_offset = 0x7a4 byteArray [ 32 bytes ] ----> 00000000 55 89 E5 81 EC 08 10 00-00 57 56 53 6A 00 68 01 U夊侅....WVSj.h. 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 00 68 01 U夊侅....WVSj.h. 00000010 8A 04 08 E8 F0 FC FF FF-89 85 FC EF FF FF 83 C4 ?.桊?墔兡 $ 可以用objdump -p <...>查看程序加载地址,加载地址之后的都可以正常文件I/O读 取,加载地址之前的无法正常文件I/O读取。这个说法欠妥,应该是cat /proc//maps 查看究竟哪些内存区域建立了映射关系,然后可以正常文件I/O读取。 D: scz@nsfocus 如果这里以 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 U夊侅....WVSj.h 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 U夊侅....WVSj.h 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@nsfocus Solaris 2.6下参看getpagesize(3C)手册页,关于如何获取页大小,一般是8192。 Linux下参看getpagesize(2)手册页,一般是4096。 3.5 setitimer如何用 D: scz@nsfocus 为什么要学习使用setitimer(2),因为alarm(3)属于被淘汰的定时器技术。 A: scz@nsfocus 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@nsfocus 讨论一个问题。getchar()的作用是降低CPU占用率,可用top命令查看。 timer_sample.c中换用ITIMER_PROF/SIGPROF后,你会发现上述程序无输出,我据此 认为getchar()形成的阻塞不计算在进程虚拟时钟中,也不认为系统正在为进程利益 而运行。 如果进一步将getchar()去掉,直接一个while()无限循环,即使换用 ITIMER_PROF/SIGPROF,程序还是有输出。不过top命令查看的结果让你吐血,CPU几 乎无空闲。 D: scz@nsfocus setitimer( ITIMER_REAL, &value, NULL )导致分发SIGALRM信号,如果同时使用 alarm(),势毕造成冲突。此外注意sleep()、pause()等函数带来的冲突。 3.6 execstack命令 A: 某些Linux发行版有一个execstack命令,比如在Debian上"aptitude install execstack"。 这个命令在操作、访问ELF文件"PT_GNU_STACK program header"的p_flags字段: -------------------------------------------------------------------------- struct file struct elf_header struct program_header_table struct program_table_entry32_t program_table_element[i] (RW_) GNU_STACK enum p_type32_e p_type PT_GNU_STACK (0x6474E551) enum p_flags32_e p_flags PF_Read_Write (6) PF_None (0) PF_Exec (1) PF_Write (2) PF_Write_Exec (3) PF_Read (4) PF_Read_Exec (5) PF_Read_Write_Exec (7) struct section_header_table -------------------------------------------------------------------------- -s 将PF_Exec置位,表示该ELF文件要求栈可执行。 -c 将PF_Exec复位,表示该ELF文件不要求栈可执行。 -q 检查PF_Exec的状态: - 该ELF文件不要求栈可执行 X 该ELF文件要求栈可执行 ? 未知,比如找不到"PT_GNU_STACK program header" -------------------------------------------------------------------------- 示例: $ cp /bin/ls scz_ls $ execstack -q scz_ls - scz_ls $ execstack -s scz_ls $ execstack -q scz_ls X scz_ls -------------------------------------------------------------------------- 支持栈不可执行的kernel和dynamic linker,在"PT_GNU_STACK program header"存 在的情况下,会尊重其p_flags的设置,以此控制栈区是否可执行。 当"PT_GNU_STACK program header"不存在时,应假设该ELF文件要求栈可执行。 3.7 paxtest命令 A: apt-get install paxtest paxtest试图检查当前系统的DEP、ASLR设置。我这个版本的paxtest有两种模式, kiddie和blackhat,我没看出来有什么区别。 # paxtest blackhat Writing output to paxtest.log Mode: blackhat Linux debian 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686 GNU/Linux Executable anonymous mapping : Vulnerable Executable bss : Vulnerable Executable data : Vulnerable Executable heap : Vulnerable Executable stack : Vulnerable Executable anonymous mapping (mprotect) : Vulnerable Executable bss (mprotect) : Vulnerable Executable data (mprotect) : Vulnerable Executable heap (mprotect) : Vulnerable Executable shared library bss (mprotect) : Vulnerable Executable shared library data (mprotect): Vulnerable Executable stack (mprotect) : Vulnerable Anonymous mapping randomisation test : 9 bits (guessed) Heap randomisation test (ET_EXEC) : No randomisation Heap randomisation test (ET_DYN) : No randomisation Main executable randomisation (ET_EXEC) : 10 bits (guessed) Main executable randomisation (ET_DYN) : 10 bits (guessed) Shared library randomisation test : 10 bits (guessed) Stack randomisation test (SEGMEXEC) : 19 bits (guessed) Stack randomisation test (PAGEEXEC) : 19 bits (guessed) Return to function (strcpy) : Vulnerable Return to function (strcpy, RANDEXEC) : Vulnerable Return to function (memcpy) : Vulnerable Return to function (memcpy, RANDEXEC) : Vulnerable Executable shared library bss : Vulnerable Executable shared library data : Killed Writable text segments : Vulnerable "Executable ..."测试,将一条指令放在相应内存,并试图执行它。 "... (mprotect)"测试,调用mprotect()将相应内存设置为可执行,再尝试执行。 "Return to function ..."测试,覆盖位于栈上的RetAddr。 "Writable text segments"测试,覆盖本来就标识为可执行的内存。 执行结果中出现下列内容时,表明当前系统未就缓冲区溢出进行过加固: Vulnerable No randomisation 6 bits 3.8 如何判断当前系统启用了ASLR A: scz@nsfocus 有一个内核参数randomize_va_space用于控制系统级ASLR: -------------------------------------------------------------------------- 0 关闭ASLR 1 mmap base、stack、vdso page将随机化。这意味着.so文件将被加载到随机地址。 链接时指定了-pie选项的可执行程序,其代码段加载地址将被随机化。配置内核 时如果指定了CONFIG_COMPAT_BRK,randomize_va_space缺省为1。此时heap没有 随机化。 2 在1的基础上增加了heap随机化。配置内核时如果禁用CONFIG_COMPAT_BRK, randomize_va_space缺省为2。 -------------------------------------------------------------------------- 查询randomize_va_space当前设置: # sysctl -n kernel.randomize_va_space 1 # cat /proc/sys/kernel/randomize_va_space 1 关闭ASLR: # sysctl -w kernel.randomize_va_space=0 # echo 0 > /proc/sys/kernel/randomize_va_space A: scz@nsfocus 可以用ldd快速判断当前系统是否启用了ASLR: $ ldd `which col` linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7ded000) /lib/ld-linux.so.2 (0x80000000) $ ldd `which col` linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7db5000) /lib/ld-linux.so.2 (0x80000000) $ ldd `which col` linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7db0000) /lib/ld-linux.so.2 (0x80000000) 注意,本例中有两个so的加载基址未被随机化。 A: Mkrtich Soghomonyan 2011-03-25 $ cat /proc/self/maps > 1.txt $ cat /proc/self/maps > 2.txt $ diff 1.txt 2.txt 4,15c4,15 < b7bb1000-b7db1000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive < b7db1000-b7db2000 rw-p b7db1000 00:00 0 < b7db2000-b7ef2000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so < b7ef2000-b7ef4000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so < b7ef4000-b7ef5000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so < b7ef5000-b7ef8000 rw-p b7ef5000 00:00 0 < b7f09000-b7f0b000 rw-p b7f09000 00:00 0 < b7f0b000-b7f0c000 r-xp b7f0b000 00:00 0 [vdso] < b7f0c000-b7f27000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so < b7f27000-b7f28000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so < b7f28000-b7f29000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so < bfe27000-bfe3d000 rw-p bfe27000 00:00 0 [stack] --- > b7bfa000-b7dfa000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive > b7dfa000-b7dfb000 rw-p b7dfa000 00:00 0 > b7dfb000-b7f3b000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so > b7f3b000-b7f3d000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so > b7f3d000-b7f3e000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so > b7f3e000-b7f41000 rw-p b7f3e000 00:00 0 > b7f52000-b7f54000 rw-p b7f52000 00:00 0 > b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso] > b7f55000-b7f70000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so > b7f70000-b7f71000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so > b7f71000-b7f72000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so > bfa6e000-bfa83000 rw-p bfa6e000 00:00 0 [stack] diff输出表明stack、vdso page、一些so的地址均被随机化。 $ cat 1.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7bb1000-b7db1000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7db1000-b7db2000 rw-p b7db1000 00:00 0 b7db2000-b7ef2000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7ef2000-b7ef4000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7ef4000-b7ef5000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7ef5000-b7ef8000 rw-p b7ef5000 00:00 0 b7f09000-b7f0b000 rw-p b7f09000 00:00 0 b7f0b000-b7f0c000 r-xp b7f0b000 00:00 0 [vdso] b7f0c000-b7f27000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7f27000-b7f28000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7f28000-b7f29000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bfe27000-bfe3d000 rw-p bfe27000 00:00 0 [stack] $ cat 2.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7bfa000-b7dfa000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7dfa000-b7dfb000 rw-p b7dfa000 00:00 0 b7dfb000-b7f3b000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f3b000-b7f3d000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f3d000-b7f3e000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f3e000-b7f41000 rw-p b7f3e000 00:00 0 b7f52000-b7f54000 rw-p b7f52000 00:00 0 b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso] b7f55000-b7f70000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7f70000-b7f71000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7f71000-b7f72000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bfa6e000-bfa83000 rw-p bfa6e000 00:00 0 [stack] 检查原始输出,主映像、heap未被随机化。 关闭ASLR之后再测试一遍: # sysctl -w kernel.randomize_va_space=0 kernel.randomize_va_space = 0 # cat /proc/self/maps > 1.txt # cat /proc/self/maps > 2.txt # diff 1.txt 2.txt diff无输出,表示各内存区域基址未被随机化。 # cat 1.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7c8a000-b7e8a000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] 内核参数randomize_va_space置0会关闭整个系统的ASLR,有时候只想关闭单个进程 的ASLR,可以用setarch命令实现这点。 $ setarch `uname -m` -R cat /proc/self/maps > 1.txt $ setarch `uname -m` -R cat /proc/self/maps > 2.txt $ diff 1.txt 2.txt diff无输出,表示各内存区域基址未被随机化。 $ cat 1.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7c8a000-b7e8a000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffea000-c0000000 rw-p bffea000 00:00 0 [stack] D: scz 2012-04-28 16:35 奇怪的是,setarch关闭单个进程ASLR时,stack的地址有时会变,并不总是固定的, 这是什么情况?而内核参数randomize_va_space置0时,就没有观察到这种现象。 $ setarch `uname -m` -R cat /proc/self/maps > 1.txt $ setarch `uname -m` -R cat /proc/self/maps > 2.txt $ diff 1.txt 2.txt 15c15 < bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] --- > bffea000-c0000000 rw-p bffea000 00:00 0 [stack] $ cat 1.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7c8a000-b7e8a000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] 3.9 如何关闭ASLR A: scz 将内核参数randomize_va_space置0,会在系统范围内关闭ASLR: # sysctl -w kernel.randomize_va_space=0 # echo 0 > /proc/sys/kernel/randomize_va_space A: Mkrtich Soghomonyan 2011-03-25 内核参数randomize_va_space置0会关闭整个系统的ASLR,有时候只想关闭单个进程 的ASLR,可以用setarch命令实现这点。 $ setarch `uname -m` -R cat /proc/self/maps > 1.txt $ setarch `uname -m` -R cat /proc/self/maps > 2.txt $ diff 1.txt 2.txt 奇怪的是,setarch关闭单个进程ASLR时,stack的地址有时会变,并不总是固定的, 而内核参数randomize_va_space置0时,就没有观察到这种现象。 A: Mkrtich Soghomonyan 2011-03-25 我们来研究一下"setarch -R"到底干了什么。 # echo 0 > /proc/sys/kernel/randomize_va_space # strace -o 1.txt setarch `uname -m` -R which col /usr/bin/col # strace -o 2.txt setarch `uname -m` which col /usr/bin/col # diff 1.txt 2.txt ... 80c80 < personality(0x40008 /* PER_??? */) = 0 --- > personality(PER_LINUX32) = 0 ... 排除明显无意义的区别,注意到personality( 0x40008 )。"man setarch"时看到-R 参数对应ADDR_NO_RANDOMIZE,在头文件里搜一下: # find /usr/include -name "*.h" -exec grep -Hn ADDR_NO_RANDOMIZE {} \; /usr/include/sys/personality.h:30: ADDR_NO_RANDOMIZE = 0x0040000, /usr/include/linux/personality.h:11: ADDR_NO_RANDOMIZE = 0x0040000, /* disable randomization of VA space */ /usr/include/linux/personality.h:29:#define PER_CLEAR_ON_SETID (READ_IMPLIES_EXEC|ADDR_NO_RANDOMIZE) 在"sys/personality.h"中找到两个值: ADDR_NO_RANDOMIZE = 0x0040000 PER_LINUX32 = 0x0008 0x40008就是"ADDR_NO_RANDOMIZE|PER_LINUX32"。很明显,"setarch -R"主要就是调 用personality( ADDR_NO_RANDOMIZE | PER_LINUX32 )。 一般来说,为了编程关闭单个进程的ASLR,先fork()出子进程,在子进程中调用: personality( original_persona | ADDR_NO_RANDOMIZE ) 然后exec...()。 下面写程序测试personality()的行为: -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o personality_demo personality_demo.c * gcc-3.3 -Wall -pipe -g -o personality_demo personality_demo.c */ #include #include #include #include #include #include #include #include #include #include int main ( int argc, char * argv[] ) { pid_t pid; int original_persona; int test_persona; int status; struct user_regs_struct regs; if ( argc > 1 ) { test_persona = 0; } else { test_persona = ADDR_NO_RANDOMIZE; } pid = fork(); if ( pid < 0 ) { perror( "fork() failed" ); exit( EXIT_FAILURE ); } if ( 0 == pid ) { /* * 子进程 */ errno = 0; ptrace( PT_TRACE_ME, 0, 0, 0 ); if ( errno ) { perror( "child ptrace( PT_TRACE_ME ) failed" ); exit( EXIT_FAILURE ); } else { original_persona = personality( 0xffffffff ); if ( -1 == original_persona ) { perror( "personality( 0xffffffff ) failed" ); exit( EXIT_FAILURE ); } if ( -1 == personality( original_persona | test_persona ) ) { perror( "personality( original_persona | test_persona ) failed" ); exit( EXIT_FAILURE ); } if ( -1 == execl( "/usr/bin/which", "which", "col", NULL ) ) { perror( "execl() failed" ); } /* * 保护性退出 */ exit( EXIT_FAILURE ); } } else { /* * 父进程 */ printf( "child = %u\n", pid ); wait( &status ); while ( WIFSTOPPED( status ) ) { #if 0 /* * 这个值一般是0x57F或1407,有些人会直接判断status是否等于这个 * 值,我们不建议这种山寨搞法。 */ printf( "status = 0x%08X\n", ( unsigned int )status ); #endif if ( -1 == ptrace( PTRACE_GETREGS, pid, 0, ®s ) ) { perror( "child ptrace( PTRACE_GETREGS ) failed" ); } #if 0 /* * /usr/include/sys/user.h * * struct user_regs_struct * { * long int ebx; * long int ecx; * long int edx; * long int esi; * long int edi; * long int ebp; * long int eax; * long int xds; * long int xes; * long int xfs; * long int xgs; * long int orig_eax; * long int eip; * long int xcs; * long int eflags; * long int esp; * long int xss; * }; */ printf ( "eax = 0x%08X\n" "ebx = 0x%08X\n" "ecx = 0x%08X\n" "edx = 0x%08X\n" "esi = 0x%08X\n" "edi = 0x%08X\n" "ebp = 0x%08X\n" "esp = 0x%08X\n" "eip = 0x%08X\n" "eflags = 0x%08X\n" "orig_eax = 0x%08X\n", ( unsigned int )regs.eax, ( unsigned int )regs.ebx, ( unsigned int )regs.ecx, ( unsigned int )regs.edx, ( unsigned int )regs.esi, ( unsigned int )regs.edi, ( unsigned int )regs.ebp, ( unsigned int )regs.esp, ( unsigned int )regs.eip, ( unsigned int )regs.eflags, ( unsigned int )regs.orig_eax ); getchar(); #else /* * 仅仅是为了产生一次阻塞,给个机会执行"cat /proc//maps"。 * 为什么选0xB?这纯属实验结果,因为0xB只出现了一次。 */ if ( 0xB == regs.orig_eax ) { getchar(); } #endif if ( -1 == ptrace( PTRACE_SYSCALL, pid, 0, 0 ) ) { perror( "child ptrace( PTRACE_SYSCALL ) failed" ); } wait( &status ); } /* end of while */ } return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 先启用全局ASLR: # sysctl -w kernel.randomize_va_space=2 kernel.randomize_va_space = 2 测试ASLR的效果: # ./personality_demo no child = 28684 /usr/bin/col # ./personality_demo no child = 28691 /usr/bin/col # ./personality_demo no child = 28696 /usr/bin/col # # cat /proc/28684/maps > 1.txt # cat /proc/28691/maps > 2.txt # cat /proc/28696/maps > 3.txt # diff 1.txt 2.txt 4,7c4,7 < b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso] < b7f55000-b7f70000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so < b7f70000-b7f72000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so < bfebf000-bfed4000 rw-p bfebf000 00:00 0 [stack] --- > b7fb1000-b7fb2000 r-xp b7fb1000 00:00 0 [vdso] > b7fb2000-b7fcd000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so > b7fcd000-b7fcf000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so > bfcb4000-bfcca000 rw-p bfcb4000 00:00 0 [stack] # diff 1.txt 3.txt 4,7c4,7 < b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso] < b7f55000-b7f70000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so < b7f70000-b7f72000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so < bfebf000-bfed4000 rw-p bfebf000 00:00 0 [stack] --- > b7fcb000-b7fcc000 r-xp b7fcb000 00:00 0 [vdso] > b7fcc000-b7fe7000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so > b7fe7000-b7fe9000 rw-p 0001a000 08:01 1046552 /lib/ld-2.11.2.so > bfc70000-bfc86000 rw-p bfc70000 00:00 0 [stack] # 我这个系统上randomize_va_space置为2,heap也没有被随机化啊,不给力。 # uname -a Linux debian 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686 GNU/Linux 接着测试关闭单个进程ASLR的效果: # ./personality_demo child = 28709 /usr/bin/col # ./personality_demo child = 28714 /usr/bin/col # ./personality_demo child = 28717 /usr/bin/col # # cat /proc/28709/maps > 4.txt # cat /proc/28714/maps > 5.txt # cat /proc/28717/maps > 6.txt # diff 4.txt 5.txt # diff 4.txt 6.txt 7c7 < bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] --- > bffea000-c0000000 rw-p bffea000 00:00 0 [stack] # 除了stack有时候在变,其它的ASLR都消失了。 看上去personality( ADDR_NO_RANDOMIZE )对stack的处理有点古怪。 D: scz 对setuid程序,Linux内核会清除传递给personality()的如下标志位: ADDR_NO_RANDOMIZE ADDR_COMPAT_LAYOUT MMAP_PAGE_ZERO READ_IMPLIES_EXEC 即使当前用户是root。 3.10 GCC编译链接时去除栈保护 A: 有时候出于很特别的理由,比如你在学习BO,可能不想启用一些高版本GCC缺省启用 的安全机制,比如栈保护: gcc -O0 -g -Wa,--execstack -fno-stack-protector -mpreferred-stack-boundary=2 -o vultest vultest.c gcc -O0 -g -Wl,-z,execstack -fno-stack-protector -mpreferred-stack-boundary=2 -o vultest vultest.c gcc -O0 -g -z execstack -fno-stack-protector -mpreferred-stack-boundary=2 -o vultest vultest.c "-z execstack"就是将"PT_GNU_STACK program header"的p_flags字段PF_Exec置位。 "-fno-stack-protector"的对立面,其主要目的是保护栈上的RetAddr,参看: http://en.wikipedia.org/wiki/Buffer_overflow_protection#GCC_Stack-Smashing_Protector_.28ProPolice.29 3.11 Linux系统的DEP A: scz@nsfocus 某些Linux发行版(RedHat/Fedora)有一个内核参数exec-shield用于控制系统级DEP: -------------------------------------------------------------------------- 0 Exec-shield (including randomized VM mapping) is disabled for all binaries, marked or not 1 Exec-shield is enabled for all marked binaries 这是缺省值。所谓marked,指"PT_GNU_STACK program header"存在。 2 Exec-shield is enabled for all binaries, regardless of marking (To be used for testing purposes ONLY) -------------------------------------------------------------------------- 关闭系统级DEP: sysctl -w kernel.exec-shield=0 echo 0 > /proc/sys/kernel/exec-shield 另有一个与ASLR相关的内核参数exec-shield-randomize: -------------------------------------------------------------------------- 0 Randomized VM mapping is disabled 1 Randomized VM mapping is enabled 这是缺省值。 -------------------------------------------------------------------------- 关闭系统级ASLR: sysctl -w kernel.exec-shield-randomize=0 echo 0 > /proc/sys/kernel/exec-shield-randomize 但上述两个内核参数在我的系统上都没有: # uname -a Linux debian 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686 GNU/Linux 此时必须用别的办法判断当前系统是否启用DEP。为方便后续的diff操作,关闭ASLR 进行测试: $ setarch `uname -m` -R cat /proc/self/maps | tee 1.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7c8a000-b7e8a000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffea000-c0000000 rw-p bffea000 00:00 0 [stack] 从显示中可以看出,heap、stack都没有x权限,当前系统启用了DEP。 $ execstack -q `which cat` - /bin/cat cat不要求栈可执行。 可以用"setarch -X"关闭单个进程的部分DEP: $ setarch `uname -m` -RX cat /proc/self/maps | tee 2.txt 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rwxp 00006000 08:01 196226 /bin/cat 08050000-08071000 rwxp 08050000 00:00 0 [heap] b7c8a000-b7e8a000 r-xp 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e8a000-b7e8b000 rwxp b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r-xp 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rwxp 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rwxp b7fce000 00:00 0 b7fe2000-b7fe4000 rwxp b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r-xp 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rwxp 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] $ diff 1.txt 2.txt 2,5c2,5 < 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat < 08050000-08071000 rw-p 08050000 00:00 0 [heap] < b7c8a000-b7e8a000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive < b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 --- > 0804f000-08050000 rwxp 00006000 08:01 196226 /bin/cat > 08050000-08071000 rwxp 08050000 00:00 0 [heap] > b7c8a000-b7e8a000 r-xp 00000000 08:01 98130 /usr/lib/locale/locale-archive > b7e8a000-b7e8b000 rwxp b7e8a000 00:00 0 7,10c7,10 < b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so < b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so < b7fce000-b7fd1000 rw-p b7fce000 00:00 0 < b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 --- > b7fcb000-b7fcd000 r-xp 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so > b7fcd000-b7fce000 rwxp 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so > b7fce000-b7fd1000 rwxp b7fce000 00:00 0 > b7fe2000-b7fe4000 rwxp b7fe2000 00:00 0 13,15c13,15 < b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so < b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so < bffea000-c0000000 rw-p bffea000 00:00 0 [stack] --- > b8000000-b8001000 r-xp 0001a000 08:01 1046552 /lib/ld-2.11.2.so > b8001000-b8002000 rwxp 0001b000 08:01 1046552 /lib/ld-2.11.2.so > bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] "setarch -X"未能让stack可执行,但heap已经可执行。 # strace -o 3.txt setarch `uname -m` -R cat /proc/self/maps > /dev/null # strace -o 4.txt setarch `uname -m` -RX cat /proc/self/maps > /dev/null # diff 3.txt 4.txt ... 80c80 < personality(0x40008 /* PER_??? */) = 0 --- > personality(0x440008 /* PER_??? */) = 0 ... 在"sys/personality.h"中找到两个值: ADDR_NO_RANDOMIZE = 0x0040000 READ_IMPLIES_EXEC = 0x0400000 PER_LINUX32 = 0x0008 "setarch -X"对应系统调用personality( READ_IMPLIES_EXEC )。 一般来说,为了编程关闭单个进程的DEP,先fork()出子进程,在子进程中调用: personality( original_persona | READ_IMPLIES_EXEC ) 然后exec...()。 D: scz@nsfocus 为了让stack可执行,目前只找到"execstack -s"这一种办法。 $ cp `which cat` scz_cat $ execstack -s scz_cat $ ./scz_cat /proc/self/maps 08048000-0804f000 r-xp 00000000 08:01 378821 /tmp/scz_cat 0804f000-08050000 rwxp 00006000 08:01 378821 /tmp/scz_cat 08050000-08071000 rwxp 08050000 00:00 0 [heap] b7c2c000-b7e2c000 r-xp 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e2c000-b7e2d000 rwxp b7e2c000 00:00 0 b7e2d000-b7f6d000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f6d000-b7f6f000 r-xp 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f6f000-b7f70000 rwxp 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f70000-b7f73000 rwxp b7f70000 00:00 0 b7f84000-b7f86000 rwxp b7f84000 00:00 0 b7f86000-b7f87000 r-xp b7f86000 00:00 0 [vdso] b7f87000-b7fa2000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7fa2000-b7fa3000 r-xp 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7fa3000-b7fa4000 rwxp 0001b000 08:01 1046552 /lib/ld-2.11.2.so bfa90000-bfaa5000 rwxp bfa90000 00:00 0 [stack] $ cat /proc/self/maps 08048000-0804f000 r-xp 00000000 08:01 196226 /bin/cat 0804f000-08050000 rw-p 00006000 08:01 196226 /bin/cat 08050000-08071000 rw-p 08050000 00:00 0 [heap] b7c21000-b7e21000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e21000-b7e22000 rw-p b7e21000 00:00 0 b7e22000-b7f62000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f62000-b7f64000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f64000-b7f65000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7f65000-b7f68000 rw-p b7f65000 00:00 0 b7f79000-b7f7b000 rw-p b7f79000 00:00 0 b7f7b000-b7f7c000 r-xp b7f7b000 00:00 0 [vdso] b7f7c000-b7f97000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b7f97000-b7f98000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b7f98000-b7f99000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bfd58000-bfd6e000 rw-p bfd58000 00:00 0 [stack] 3.12 exec*()对ASLR的影响 D: -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o first first.c */ #include #include int main ( int argc, char * argv[] ) { char var[64]; printf( "var is at %p\n", &var ); execl( "./second", NULL ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o second second.c */ #include int main ( int argc, char * argv[] ) { char buf[64]; printf( "buf is at %p\n", &buf ); return( 1 ); } /* end of main */ -------------------------------------------------------------------------- $ uname -a Linux debian 2.6.18-4-686 #1 SMP Wed May 9 23:03:12 UTC 2007 i686 GNU/Linux $ for ((i=0;i<16;i++));do ./first;done var is at 0xbfc668e0 1-bits buf is at 0xbfc668f0 var is at 0xbfd211a0 * buf is at 0xbfd211a0 var is at 0xbf9465c0 1-bits buf is at 0xbf9465d0 var is at 0xbfca5920 buf is at 0xbfca5700 var is at 0xbfad9750 buf is at 0xbfa61ee0 var is at 0xbfad0750 buf is at 0xbf9eb670 var is at 0xbfb15f90 buf is at 0xbfffe480 var is at 0xbff233a0 buf is at 0xbff6e3f0 var is at 0xbf8c3540 buf is at 0xbfe752f0 var is at 0xbfde5260 buf is at 0xbfd97a10 var is at 0xbfd519d0 buf is at 0xbff6fbf0 var is at 0xbfa6f6f0 buf is at 0xbfa3a6c0 var is at 0xbfdcf250 * buf is at 0xbfdcf250 var is at 0xbfd331b0 * buf is at 0xbfd331b0 var is at 0xbf8f5570 * buf is at 0xbf8f5570 var is at 0xbf9655e0 * buf is at 0xbf9655e0 总共运行了16次first,对比exec*()之后second的stack地址,其中5次stack没有随 机化,2次stack只变了1-bits。至少在2.6.18内核上,exec*()弱化了ASLR的效果。 $ uname -r 3.4.0-scz-20120531 $ for ((i=0;i<16;i++));do ./first;done var is at 0xbf8c9f90 buf is at 0xbfdced00 var is at 0xbfbbf7e0 buf is at 0xbfd2a210 var is at 0xbf95e5a0 buf is at 0xbfa82000 var is at 0xbf9c81c0 buf is at 0xbfc48fb0 var is at 0xbfe39eb0 buf is at 0xbfdf45a0 var is at 0xbff18cd0 buf is at 0xbfb2d0b0 var is at 0xbf85af20 buf is at 0xbf8041e0 var is at 0xbf988880 buf is at 0xbf805130 var is at 0xbfae2920 buf is at 0xbf855410 var is at 0xbf90cf80 buf is at 0xbfe7acc0 var is at 0xbf8e8640 buf is at 0xbfbcc7c0 var is at 0xbf96e050 buf is at 0xbf94e590 var is at 0xbf969b20 buf is at 0xbf8d28e0 var is at 0xbfa1dba0 buf is at 0xbfc209d0 var is at 0xbf953320 buf is at 0xbffa45b0 var is at 0xbfdeafb0 buf is at 0xbffc4980 新版本的内核上,已经不存在exec*()弱化ASLR效果的问题。 3.13 如何获取ELF文件的入口点(e_entry) A: scz $ objdump -f `which id` | grep start start address 0x08048e60 $ readelf -h `which id` | grep "Entry point" Entry point address: 0x8048e60 $ LD_SHOW_AUXV=1 id | grep AT_ENTRY AT_ENTRY: 0x8048e60 $ gdb id GNU gdb (GDB) 7.0.1-debian (gdb) info files Symbols from "/usr/bin/id". Local exec file: `/usr/bin/id', file type elf32-i386. Entry point: 0x8048e60 0x08048114 - 0x08048127 is .interp 0x08048128 - 0x08048148 is .note.ABI-tag 0x08048148 - 0x080482c4 is .hash 0x080482c4 - 0x0804830c is .gnu.hash 0x0804830c - 0x0804868c is .dynsym 0x0804868c - 0x080488e7 is .dynstr 0x080488e8 - 0x08048958 is .gnu.version 0x08048958 - 0x080489c8 is .gnu.version_r 0x080489c8 - 0x080489e8 is .rel.dyn 0x080489e8 - 0x08048b50 is .rel.plt 0x08048b50 - 0x08048b80 is .init 0x08048b80 - 0x08048e60 is .plt 0x08048e60 - 0x0804d25c is .text 0x0804d25c - 0x0804d278 is .fini 0x0804d280 - 0x0804e11c is .rodata 0x0804e11c - 0x0804e120 is .eh_frame 0x0804f120 - 0x0804f128 is .ctors 0x0804f128 - 0x0804f130 is .dtors 0x0804f130 - 0x0804f134 is .jcr 0x0804f134 - 0x0804f20c is .dynamic 0x0804f20c - 0x0804f214 is .got 0x0804f214 - 0x0804f2d4 is .got.plt 0x0804f2d4 - 0x0804f2f8 is .data 0x0804f300 - 0x0804f4a4 is .bss (gdb) 在运行状态下,还可以用"info auxv": $ gdb id GNU gdb (GDB) 7.0.1-debian (gdb) info files Symbols from "/usr/bin/id". Local exec file: `/usr/bin/id', file type elf32-i386. Entry point: 0x8048e60 ... (gdb) info auxv 32 AT_SYSINFO Special system info/entry points 0xb7fe4400 33 AT_SYSINFO_EHDR System-supplied DSO's ELF header 0xffffe000 16 AT_HWCAP Machine-dependent CPU capability hints 0xfebfbff 6 AT_PAGESZ System page size 4096 17 AT_CLKTCK Frequency of times() 100 3 AT_PHDR Program headers for program 0x8048034 4 AT_PHENT Size of program header entry 32 5 AT_PHNUM Number of program headers 7 7 AT_BASE Base address of interpreter 0xb7fe5000 8 AT_FLAGS Flags 0x0 9 AT_ENTRY Entry point of program 0x8048e60 11 AT_UID Real user ID 0 12 AT_EUID Effective user ID 0 13 AT_GID Real group ID 0 14 AT_EGID Effective group ID 0 23 AT_SECURE Boolean, was exec setuid-like? 0 15 AT_PLATFORM String identifying platform 0xbffffe3b "i686" 0 AT_NULL End of vector 0x0 (gdb) 对于"not stripped"的ELF文件,_start()就是入口点: (gdb) print/x (unsigned int)_start $1 = 0x804a528 (gdb) b *_start Breakpoint 1 at 0x804a528 (gdb) disas _start Dump of assembler code for function _start: 0x0804a528 <_start+0>: xor %ebp,%ebp 0x0804a52a <_start+2>: pop %esi 0x0804a52b <_start+3>: mov %esp,%ecx 0x0804a52d <_start+5>: and $0xfffffff0,%esp 0x0804a530 <_start+8>: push %eax 0x0804a531 <_start+9>: push %esp 0x0804a532 <_start+10>: push %edx 0x0804a533 <_start+11>: push $0x805f2c4 0x0804a538 <_start+16>: push $0x805f27c 0x0804a53d <_start+21>: push %ecx 0x0804a53e <_start+22>: push %esi 0x0804a53f <_start+23>: push $0x804b8c4 // 这个就是main() 0x0804a544 <_start+28>: call 0x804a268 <__libc_start_main@plt> 0x0804a549 <_start+33>: hlt 0x0804a54a <_start+34>: nop 0x0804a54b <_start+35>: nop End of assembler dump. (gdb) info function ^_start All functions matching regular expression "^_start": Non-debugging symbols: 0x0804a528 _start (gdb) info address _start Symbol "_start" is at 0x804a528 in a file compiled without debugging. 3.14 如何获取ELF文件加载基址 Q: 在windbg中可以"lm m <...>"获取指定模块的加载基址,在Linux上如何获取ELF文件 加载基址? A: scz # sleep 6000 # cat /proc/`ps auwx | grep "sleep 6000" | grep -v grep | awk '{print $2;}'`/maps # cat /proc/`pidof -s sleep`/maps 08048000-0804e000 r-xp 00000000 08:01 201477 /bin/sleep 0804e000-0804f000 rw-p 00005000 08:01 201477 /bin/sleep 0804f000-08070000 rw-p 0804f000 00:00 0 [heap] b7c8a000-b7e8a000 r--p 00000000 08:01 98130 /usr/lib/locale/locale-archive b7e8a000-b7e8b000 rw-p b7e8a000 00:00 0 b7e8b000-b7fcb000 r-xp 00000000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcb000-b7fcd000 r--p 0013f000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fcd000-b7fce000 rw-p 00141000 08:01 769046 /lib/i686/cmov/libc-2.11.2.so b7fce000-b7fd1000 rw-p b7fce000 00:00 0 b7fe2000-b7fe4000 rw-p b7fe2000 00:00 0 b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] b7fe5000-b8000000 r-xp 00000000 08:01 1046552 /lib/ld-2.11.2.so b8000000-b8001000 r--p 0001a000 08:01 1046552 /lib/ld-2.11.2.so b8001000-b8002000 rw-p 0001b000 08:01 1046552 /lib/ld-2.11.2.so bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack] 从/proc//maps中可以看到加载基址,本例中是0x08048000。一般来说,对于指 定,用如下命令获取加载基址: # cat /proc//maps | grep -m 1 r-xp | cut -d'-' -f1 还可以用objdump查看第一个LOAD段的vaddr,其实就是ELF文件加载基址: # objdump -p `which id` | grep -A 1 LOAD LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12 filesz 0x00006120 memsz 0x00006120 flags r-x LOAD off 0x00006120 vaddr 0x0804f120 paddr 0x0804f120 align 2**12 filesz 0x000001d8 memsz 0x00000384 flags rw- "-m 1"表示只匹配1次就停止,"-A 1"表示同时显示匹配行后的1行: # objdump -p `which id` | grep -m 1 -A 1 LOAD LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12 filesz 0x00006120 memsz 0x00006120 flags r-x 基于同样的思路,readelf亦可用于此目的: # readelf -l `which id` Elf file type is EXEC (Executable file) Entry point 0x8048e60 There are 7 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4 INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x06120 0x06120 R E 0x1000 LOAD 0x006120 0x0804f120 0x0804f120 0x001d8 0x00384 RW 0x1000 DYNAMIC 0x006134 0x0804f134 0x0804f134 0x000d8 0x000d8 RW 0x4 NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag 06 # readelf -l `which id` | grep -m 1 LOAD | awk -F' ' '{print $3;}' 0x08048000 一般来说,对于指定ELF文件,用如下命令获取加载基址: # objdump -p | grep -m 1 LOAD | awk -F' ' '{print $5;}' # readelf -l | grep -m 1 LOAD | awk -F' ' '{print $3;}' 如果是在gdb中,可以用"info proc mappings": # gdb id GNU gdb (GDB) 7.0.1-debian (gdb) info files Symbols from "/usr/bin/id". Local exec file: `/usr/bin/id', file type elf32-i386. Entry point: 0x8048e60 0x08048114 - 0x08048127 is .interp 0x08048128 - 0x08048148 is .note.ABI-tag 0x08048148 - 0x080482c4 is .hash 0x080482c4 - 0x0804830c is .gnu.hash 0x0804830c - 0x0804868c is .dynsym 0x0804868c - 0x080488e7 is .dynstr 0x080488e8 - 0x08048958 is .gnu.version 0x08048958 - 0x080489c8 is .gnu.version_r 0x080489c8 - 0x080489e8 is .rel.dyn 0x080489e8 - 0x08048b50 is .rel.plt 0x08048b50 - 0x08048b80 is .init 0x08048b80 - 0x08048e60 is .plt 0x08048e60 - 0x0804d25c is .text 0x0804d25c - 0x0804d278 is .fini 0x0804d280 - 0x0804e11c is .rodata 0x0804e11c - 0x0804e120 is .eh_frame 0x0804f120 - 0x0804f128 is .ctors 0x0804f128 - 0x0804f130 is .dtors 0x0804f130 - 0x0804f134 is .jcr 0x0804f134 - 0x0804f20c is .dynamic 0x0804f20c - 0x0804f214 is .got 0x0804f214 - 0x0804f2d4 is .got.plt 0x0804f2d4 - 0x0804f2f8 is .data 0x0804f300 - 0x0804f4a4 is .bss (gdb) b *0x8048e60 Breakpoint 1 at 0x8048e60 (gdb) r Starting program: /usr/bin/id Breakpoint 1, 0x08048e60 in ?? () (gdb) info proc mappings process 6713 cmdline = '/usr/bin/id' cwd = '/root/src' exe = '/usr/bin/id' Mapped address spaces: Start Addr End Addr Size Offset objfile 0x8048000 0x804f000 0x7000 0 /usr/bin/id 0x804f000 0x8050000 0x1000 0x6000 /usr/bin/id 0x8050000 0x8071000 0x21000 0x8050000 [heap] 0xb7e6c000 0xb7e6d000 0x1000 0xb7e6c000 0xb7e6d000 0xb7e6f000 0x2000 0 /lib/i686/cmov/libdl-2.11.2.so 0xb7e6f000 0xb7e70000 0x1000 0x1000 /lib/i686/cmov/libdl-2.11.2.so 0xb7e70000 0xb7e71000 0x1000 0x2000 /lib/i686/cmov/libdl-2.11.2.so 0xb7e71000 0xb7e72000 0x1000 0xb7e71000 0xb7e72000 0xb7fb2000 0x140000 0 /lib/i686/cmov/libc-2.11.2.so 0xb7fb2000 0xb7fb4000 0x2000 0x13f000 /lib/i686/cmov/libc-2.11.2.so 0xb7fb4000 0xb7fb5000 0x1000 0x141000 /lib/i686/cmov/libc-2.11.2.so 0xb7fb5000 0xb7fb8000 0x3000 0xb7fb5000 0xb7fb8000 0xb7fcf000 0x17000 0 /lib/libselinux.so.1 0xb7fcf000 0xb7fd1000 0x2000 0x16000 /lib/libselinux.so.1 0xb7fe2000 0xb7fe4000 0x2000 0xb7fe2000 0xb7fe4000 0xb7fe5000 0x1000 0xb7fe4000 [vdso] 0xb7fe5000 0xb8000000 0x1b000 0 /lib/ld-2.11.2.so 0xb8000000 0xb8001000 0x1000 0x1a000 /lib/ld-2.11.2.so 0xb8001000 0xb8002000 0x1000 0x1b000 /lib/ld-2.11.2.so 0xbffeb000 0xc0000000 0x15000 0xbffeb000 [stack] (gdb) 此外,"info proc stat"也可以得到加载基址: (gdb) info proc stat ... Start of text: 0x8048000 End of text: 0x804e120 Start of stack: 0xbffffd40 (gdb) D: scz@nsfocus 2013-12-05 15:57 gdb中"info sharedlibrary"看到的起始地址不是so的基址,而是so的e_entry,在IDA 里Rebase时需要减去e_entry的offset。获取e_entry的offset: objdump -f foo.so | grep start readelf -h foo.so | grep "Entry point" windbg里"lm m ..."看到的就是基址。 (gdb) info sharedlibrary upload_limit.so From To Syms Read Shared Object Library 0x4cfc1d50 0x4cfc7ca4 Yes /tmp/foo.so (gdb) x/16bm 0x4cfc1d50 0x4cfc1d50 55 89 E5 53 83 EC 04 E8 00 00 00 00 5B 81 C3 98 U..S........[... (gdb) x/10i 0x4cfc1d50 0x4cfc1d50: push ebp 0x4cfc1d51: mov ebp,esp 0x4cfc1d53: push ebx 0x4cfc1d54: sub esp,0x4 0x4cfc1d57: call 0x4cfc1d5c 0x4cfc1d5c: pop ebx 0x4cfc1d5d: add ebx,0x8298 0x4cfc1d63: mov edx,DWORD PTR [ebx-0x30] 0x4cfc1d69: test edx,edx 0x4cfc1d6b: je 0x4cfc1d72 0x4cfc1d50明显不是so的加载基址,用IDA反汇编foo.so,Rebase时指定0x4cfc1000。 0x4cfc1d50实际是.text的起始地址,也就是ELF文件的入口点(e_entry): $ objdump -f /tmp/foo.so | grep start start address 0x00000d50 $ readelf -h /tmp/foo.so | grep "Entry point" Entry point address: 0xd50 3.15 sigaltstack(2) A: scz@nsfocus 2013-12-16 17:10 关于线程栈、信号栈,巨坑无数,给个小结: -------------------------------------------------------------------------- 1) 每个线程有自己的线程栈,主线程的线程栈就是进程栈。 2) 信号句柄使用当前线程栈,或者说信号栈就是线程栈。只有线程级的信号栈,没有进 程级的信号栈。UNP 23.1里说主线程和信号句柄使用不同的栈,我猜这是过去某些系 统的行为,现在的Linux不是这样的。 Aquawood: 老版UNP用的可能是LinuxThreads实现,而不是NPTL实现。 3) pthread_create()将会移除之前的sigaltstack(),因此切换信号栈的正确位置是线 程函数中。如果在main()里切换信号栈,子线程并不会使用切换后的信号栈,而是缺 省使用子线程的线程栈。 我是测出来的结论,实际上在pthread_create(3)中明确说了这一点。 Aquawood: kernel 2.6.16以前pthread_create()创建的子线程会错误地继承主调线程的替换信 号栈。 4) 缺省创建的的子线程天然有1页大小(4096字节)的redzone/guardpage,其内存属性为 PROT_NONE。 主线程缺省没有redzone/guardpage。 5) pthread_attr_setguardsize()不能与pthread_attr_setstack()同时出现,无论先后, 只要同时出现,guardsize就无效。换句话说,pthread_attr_setstack()之后,移除 redzone,将来的pthread_attr_getguardsize()返回0。 在这种情况下可以用mprotect()人工设置redzone,但pthread_attr_getguardsize() 不会反映这种设置,仍然返回0。 务必不要与pthread_attr_setstack(3)、pthread_attr_setstackaddr(3)一起使用 pthread_attr_setguardsize()。 只是pthread_attr_setstacksize()调整线程栈大小,不指定线程栈基址,不存在前 述问题。 假设guardsize不是pagesize的倍数,在__pthread_create_2_1()->allocate_stack() 内部会向上舍入到pagesize的倍数。man手册里说pthread_attr_getguardsize()取的 是pthread_attr_setguardsize()的形参,而不是向上舍入后的值,这句话是错的(可 能过去是对的)。参看: nptl/allocatestack.c 在allocate_stack()中有一句"pd->reported_guardsize = guardsize;",它的注释 和man是一致的,但阅读代码,发现这个注释已与实际流程不相符了, reported_guardsize记录的是向上舍入后的、实际mprotect()的guardsize,实测也 证实了这点。 6) 缺省创建的多个子线程,其线程栈紧挨着,中间有redzone。 7) sigsetjmp()所在栈帧必须大于等于触发SIGSEGV时的栈帧。调用sigsetjmp()的函数 返回后,jmpbuf就失效了。 sigsetjmp()会在jmpbuf中保存6个寄存器: ebx esi edi ebp esp 变换后的,用到了gs:0x18 eip 变换后的,用到了gs:0x18 jmpbuf中的esp、eip编、解码用到了gs:0x18,这个值在线程栈上,如果线程栈被破 坏得厉害,siglongjmp()肯定会坏菜。这种变换应该是一种安全增强。 gs:0x18对应tcbhead_t.pointer_guard,gs:0x14对应tcbhead_t.stack_guard, jmpbuf中的esp没有用stack_guard,用的是pointer_guard。 8) siglongjmp()无法返回0,如果第2形参传0,实际返回的将是1。 siglongjmp()第2形参本来是int型,传一个指针出去也没事,这样信号句柄可以与主 流程交换更多信息。 9) SIGSEGV信号句柄中siglongjmp()时,有可能再次触发SIGSEGV信号,考虑jmpbuf或 gs:0x18(在线程栈上)被破坏的情形。 -------------------------------------------------------------------------- A: scz@nsfocus 2013-12-06 17:35 -------------------------------------------------------------------------- NAME sigaltstack - 设置、获取替换信号栈信息 SYNOPSIS #include int sigaltstack ( const stack_t *ss, stack_t *oss ); Feature Test Macro Requirements for glibc (see feature_test_macros(7)): _BSD_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED || /* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L DESCRIPTION sigaltstack()允许进程设置新的替换信号栈、获取已存在的替换信号栈状态。 调用sigaction(2)时如果指定了SA_ONSTACK,信号句柄就会使用替换信号栈。 为了使用替换信号栈: 1. 分配替换信号栈所用内存 2. 用sigaltstack()通知系统替换信号栈的位置 3. 调用sigaction(2)安装信号句柄时,在sa_flags中指定SA_ONSTACK,通知系 统在替换信号栈上执行信号句柄。 步骤1、2不一定出现在3之前,可以出现在3之后。 在形参ss中指定新的替换信号栈,形参oss用来接收当前替换信号栈的信息。ss、 oss均可为NULL,此时相应功能被屏蔽。 stack_t定义如下: typedef struct { void *ss_sp; /* Base address of stack */ int ss_flags; /* Flags */ size_t ss_size; /* Number of bytes in stack */ } stack_t; 为了创建新的替换信号栈,ss->ss_flags置零,ss->ss_sp、ss->ss_size指定替 换信号栈的起始地址和大小。针对ss_size,常量SIGSTKSZ(8192)足以应付常规 需求,常量MINSIGSTKSZ(2048)对应最小尺寸。 当信号句柄在替换信号栈上被激活时,根据硬件架构的不同内核在ss->ss_sp的 基础上将替换信号栈自动对齐到合适的地址边界。 如果ss->ss_flags被置成SS_DISABLE,将禁用已存在的替换信号栈,此时其他两 个成员被忽略。 如果oss不为NULL,它用来接收本次sigaltstack()调用之前已经存在的替换信号 栈信息。oss->ss_sp、oss->ss_size返回旧的替换信号栈的起始地址和大小。 oss->ss_flags可能返回下列任一值: SS_ONSTACK(1) 进程当前正在替换信号栈上执行(有信号句柄被激活?),此时无法设置新的 替换信号栈。 SS_DISABLE(2) 目前禁用替换信号栈 参: # grep -r SS_DISABLE /usr/include/ /usr/include/bits/sigstack.h /usr/include/i386-linux-gnu/bits/sigstack.h /usr/include/asm-generic/signal.h /usr/include/i386-linux-gnu/asm/signal.h RETURN VALUE 返回0表示成功,-1表示失败,此时errno被设置成相应值。 ERRORS EFAULT ss或oss非空,但指向无效内存 EINVAL ss非空,ss->ss_flags被设置成SS_DISABLE之外的非零值 ENOMEM ss->ss_size小于MINSTKSZ EPERM 试图在替换信号栈正被使用过程中设置新的替换信号栈。 NOTES 最常见的需要使用替换信号栈的地方是处理因常规栈耗尽而触发的SIGSEGV信号, 此时SIGSEGV的信号句柄无法在常规栈上被激活,如果我们希望执行SIGSEGV信号 句柄,就必须使用替换信号栈。 常规栈有可能被耗尽,比如对栈区的使用过多以致栈顶上溢或者到达 setrlimit( RLIMIT_STACK, &rlim )所设限制。此时内核向进程发送SIGSEGV信 号。这种情况下捕捉SIGSEGV信号的唯一办法是使用替换信号栈。 Linux支持的绝大多数硬件架构,其栈向低址方向增长。sigaltstack()自动考虑 栈增长方向。 信号句柄中调用的函数与信号句柄使用同一个栈。 假设目前正在一个使用了替换信号栈的信号句柄中,又有其他信号句柄被激活, 后者将使用前者正在使用的替换信号栈。与常规栈不同,系统不会自动扩展替换 信号栈。如果替换信号栈被耗尽,后果不可预测。 成功调用execve(2)将会移除所有已存在的替换信号栈。fork(2)创建的子进程继 承父进程中替换信号栈的设置。 sigaltstack()取代了更老的sigstack()。为了向后兼容性,glibc仍然提供 sigstack()。新程序应该使用sigaltstack()。 4.2BSD的sigstack()用了一个略有区别的结构,最不便的地方是主调者必须知道 栈增长方向。 EXAMPLE stack_t ss; ss.ss_sp = malloc( SIGSTKSZ ); if ( NULL == ss.ss_sp ) { /* Handle error */; } ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; if ( -1 == sigaltstack( &ss, NULL ) ) { /* Handle error */; } SEE ALSO execve(2), setrlimit(2), sigaction(2), siglongjmp(3), sigsetjmp(3), signal(7) -------------------------------------------------------------------------- 4. 系统资源相关问题 4.0 Linux线程栈的大小 Q: 在"3.15 sigaltstack(2)"中注意到主线程的线程栈大小是0x00800000,缺省创建的 子线程的线程栈是0x00801000,这多出来的一页是怎么回事? A: scz 2013-12-20 参pthread_create(3) 对于NPTL实现,线程栈的缺省大小依赖getrlimit( RLIMIT_STACK, .. )的返回值。 参nptl/allocatestack.c allocate_stack()中用__default_stacksize初始化线程栈大小。 参nptl/nptl-init.c __pthread_initialize_minimal_internal()中将__default_stacksize赋成 getrlimit( RLIMIT_STACK, .. )的返回值。这个值实际就是: # ulimit -s 8192 8192的单位是KB,参看bash(1),搜ulimit。8192K即0x00800000。但我们用 pthread_getattr_np()返回的线程栈大小是0x00801000,多了一页。 allocate_stack()中有这么一段代码(我改了一下以增强片段的可读性): -------------------------------------------------------------------------- /* * To avoid aliasing effects on a larger scale than pages we adjust * the allocated stack size if necessary. This way allocations directly * following each other will not have aliasing problems. */ #if MULTI_PAGE_ALIASING != 0 if ( 0 == ( size % MULTI_PAGE_ALIASING ) ) { size += 0x1000; } #endif -------------------------------------------------------------------------- 为了避免"64K Aliasing Conflicts",在getrlimit( RLIMIT_STACK, .. )返回值的 基础上增了0x1000,就是我们看到多出来的一页。 MULTI_PAGE_ALIASING是个宏,在nptl/sysdeps/i386/i686/Makefile中定义: CFLAGS-pthread_create.c += -DMULTI_PAGE_ALIASING=65536 从nptl/ChangeLog看出,2003年增加了这个宏,其值为0x10000。 关于"64K Aliasing Conflicts",后面我们会讲,简单理解成cache相关的东西,定 义MULTI_PAGE_ALIASING宏是为了增强cache的性能。 allocate_stack()中有另一个宏COLORING_INCREMENT,也与cache性能相关,搜 "cache color"了解更多细节。ChangeLog中的说法是用MULTI_PAGE_ALIASING、不用 COLORING_INCREMENT。我们只需要知道前者等于0x10000,后者未定义,以此来走 allocate_stack()的流程。 如果用pthread_attr_setstack(3)、pthread_attr_setstackaddr(3)指定线程栈基址, 内部有个标志位ATTR_FLAG_STACKADDR会置位,此时allocate_stack()的流程与 MULTI_PAGE_ALIASING宏无关,避免"64K Aliasing Conflicts"是程序员的职责,这 算是一个性能暗坑,man手册里没有相应警告。 多核情况下一般每个核有自己的L1D、L1P、L2,所有核共享L3。多线程时可以将各个 线程绑定到不同的核上,参sched_getaffinity(2)、CPU_SET(3)。 我感觉多线程的线程栈碰上"64K Aliasing Conflicts"的可能性不大,一个线程去访 问另一个线程的线程栈,不大可能吧。不知线程切换时会刷新L1D吗? 参pthread_attr_setstacksize(3) 线程栈的大小在线程创建时就固定了,不会再变。只有主线程可以动态增长它的栈。 前者我可以确认,后者我不确认。 -------------------------------------------------------------------------- /* * For x86/Linux Kernel 3.2.0-4-686-pae and glibc 2.13-38 * gcc-4.7 -D_GNU_SOURCE -Wall -pipe -O0 -g3 -o pthread_attr_setstacksize_test pthread_attr_setstacksize_test.c -pthread */ #include #include #include #include #include #include #include #define STACKSIZE 0x10000 typedef struct { unsigned char *base; unsigned int size; unsigned char *end; unsigned int redzonesize; unsigned char *redzoneend; } stackinfo_t; extern void * __libc_stack_end; static void GetProcessStackInfo ( stackinfo_t *stackinfo ) { unsigned int pagesize; struct rlimit res; pagesize = ( unsigned int )sysconf( _SC_PAGESIZE ); /* * The maximum size of the process stack */ getrlimit( RLIMIT_STACK, &res ); /* * 不要用rlim_max */ stackinfo->size = res.rlim_cur; stackinfo->end = ( unsigned char * )( ( ( unsigned int )__libc_stack_end + pagesize ) & ~( pagesize - 1 ) ); stackinfo->base = stackinfo->end - stackinfo->size; return; } /* end of GetProcessStackInfo */ static void GetThreadStackInfo ( pthread_t tid, stackinfo_t *stackinfo ) { pthread_attr_t attr; void *base; size_t size; memset( &attr, 0, sizeof( attr ) ); pthread_getattr_np( tid, &attr ); pthread_attr_getstack( &attr, &base, &size ); stackinfo->size = size; stackinfo->base = base; stackinfo->end = base + size; pthread_attr_getguardsize( &attr, &size ); stackinfo->redzonesize = size; stackinfo->redzoneend = base + size; pthread_attr_destroy( &attr ); return; } /* end of GetThreadStackInfo */ static void PrintProcessStackInfo ( void ) { stackinfo_t stackinfo; GetProcessStackInfo( &stackinfo ); printf ( "\n" "Process stack information\n" "\n" "base = %p\n" "size = 0x%08X\n" "end = %p\n", stackinfo.base, stackinfo.size, stackinfo.end ); return; } /* end of PrintProcessStackInfo */ static void PrintThreadStackInfo ( unsigned int arg, pthread_t tid ) { stackinfo_t stackinfo; GetThreadStackInfo( tid, &stackinfo ); printf ( "\n" "Thread stack information [0x%08X]\n" "\n" "tid = %p\n" "base = %p\n" "size = 0x%08X\n" "end = %p\n" "redzonesize = 0x%08X\n" "redzoneend = %p\n", arg, ( void * )tid, stackinfo.base, stackinfo.size, stackinfo.end, stackinfo.redzonesize, stackinfo.redzoneend ); return; } /* end of PrintThreadStackInfo */ static void * thread_main ( void *arg ) { unsigned int i = ( unsigned int )arg; PrintThreadStackInfo( i, pthread_self() ); return( NULL ); } /* end of thread_main */ int main ( int argc, char * argv[] ) { pthread_t tid[3]; pthread_attr_t attr[3]; unsigned int i; PrintProcessStackInfo(); PrintThreadStackInfo( -1, pthread_self() ); for ( i = 0; i < 3; i++ ) { pthread_attr_init( &attr[i] ); pthread_attr_setstacksize( &attr[i], STACKSIZE ); pthread_create( &tid[i], &attr[i], &thread_main, ( void * )i ); pthread_attr_destroy( &attr[i] ); } for ( i = 0; i < 3; i++ ) { pthread_join( tid[i], NULL ); } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- # gcc-4.7 -D_GNU_SOURCE -Wall -pipe -O0 -g3 -o pthread_attr_setstacksize_test pthread_attr_setstacksize_test.c -pthread # ./pthread_attr_setstacksize_test Process stack information base = 0xbf800000 // 利用了glibc导出符号__libc_stack_end size = 0x00800000 // 主线程的线程栈大小与"ulimit -s"完全一样 end = 0xc0000000 Thread stack information [0xFFFFFFFF] tid = 0xb7e436c0 base = 0xbf800000 size = 0x00800000 // pthread_attr_getstack()获得的信息与getrlimit()相符 end = 0xc0000000 // 左闭右开区间,[0xbf800000,0xc0000000) redzonesize = 0x00000000 // 主线程缺省没有redzone/guardpage redzoneend = 0xbf800000 Thread stack information [0x00000001] tid = 0xb7e42b70 base = 0xb7e32000 // [0xb7e32000,0xb7e43000) size = 0x00011000 // pthread_attr_setstacksize()的第2形参是0x10000,最终实际多了0x1000 end = 0xb7e43000 redzonesize = 0x00001000 // 1页的redzone/guardpage redzoneend = 0xb7e33000 Thread stack information [0x00000002] tid = 0xb7e31b70 base = 0xb7e21000 // [0xb7e21000,0xb7e32000) size = 0x00011000 end = 0xb7e32000 redzonesize = 0x00001000 redzoneend = 0xb7e22000 Thread stack information [0x00000000] tid = 0xb7fddb70 base = 0xb7fcd000 // [0xb7fcd000,0xb7fde000) size = 0x00011000 end = 0xb7fde000 redzonesize = 0x00001000 redzoneend = 0xb7fce000 Q: 什么是"64K Aliasing Conflicts"? A: zyh & scz 两个内存地址的低16位相同时,会被映射到一级缓存的同一个cache line,但同一时 刻一级缓存的一个cache line只能对应其中一个内存地址,此时就会产生冲突,必然 有一个cache in,另一个cache out。这就是所谓的"64K Aliasing Conflicts"。 一级缓存的每个cache line通常对应32字节、64字节等大小的内存区域。以32字节为 例: if ( ( ( address_0 % 0x10000 ) >> 5 ) == ( ( address_1 % 0x10000 ) >> 5 ) ) { 两个内存地址映射到一级缓存的同一个cache line } 比如,在一个小时间窗口内同时访问0x00010000和0x0003000F就会触发"64K Aliasing Conflicts"。 -------------------------------------------------------------------------- unsigned char buf_0[1024]; // 假设buf_0位于0x00010000 unsigned char buf_1[1024]; // 假设buf_1位于0x00020000 unsigned int sum, i; for ( i = 0; i < 1024; i++ ) { sum += buf_0[i]; sum += buf_1[i]; } -------------------------------------------------------------------------- 这段代码就存在"64K Aliasing Conflicts",将buf_1挪到0x00021000就会消除冲突, 可以提升性能。 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: scz@nsfocus 在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@nsfocus /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@nsfocus 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,普通用户无法使用该程序。 4.4 Solaris上free()的内存如何还给OS Q: 在一台SPARC/Solaris 9上man free,看到如下一段话: After free() is executed, this space is made available for further allocation by the application, though not returned to the system. Memory is returned to the system only upon termination of the application. 现在我想强制free()的内存还给OS,怎么办。 D: valent@SMTH 2006-10-15 AIX为了解决这种问题,提供了一个环境变量: MALLOCDISCLAIM=true 代价是进程会遇到更多的Page Fault,性能相应有所损失。还可以调用mallopt使用 M_DISCLAIM命令达到类似效果。 D: scz@nsfocus 2006-10-17 受valent的启发,Goolge了一圈,参考资源如下: -------------------------------------------------------------------------- [1] For AIX Malloc Disclaim http://moka.ccr.jussieu.fr/doc_link/C/a_doc_lib/aixprggd/genprogc/malloc_disclaim.htm (介绍了MALLOCDISCLAIM=true) http://moka.ccr.jussieu.fr/doc_link/en_US/a_doc_lib/libs/basetrf1/malloc.htm (介绍了mallopt、M_DISCLAIM、PSALLOC=early) http://publib.boulder.ibm.com/infocenter/pseries/v5r3/topic/com.ibm.aix.genprogc/doc/genprogc/malloc_disclaim.htm (介绍了MALLOCOPTIONS=disclaim,从AIX 5L Version 5.3开始不应继续使用MALLOCDISCLAIM环境变量) disclaim Subroutine http://publib.boulder.ibm.com/infocenter/pseries/v5r3/topic/com.ibm.aix.basetechref/doc/basetrf1/disclaim.htm System Memory Allocation Using the malloc subsystem http://publib.boulder.ibm.com/infocenter/pseries/v5r3/topic/com.ibm.aix.genprogc/doc/genprogc/sys_mem_alloc.htm (较为详细地介绍了malloc) Developing and Porting C and C++ Applications on AIX http://www.redbooks.ibm.com/redbooks/pdfs/sg245674.pdf (介绍了disclaim、MALLOCDISCLAIM以及/etc/environment的限制) [2] For Solaris bsdmalloc(3MALLOC) http://docs.sun.com/app/docs/doc/816-5168/6mbb3hr5a?a=view http://bama.ua.edu/cgi-bin/man-cgi?bsdmalloc+3MALLOC (介绍了-lbsdmalloc,性能最好但空间利用率低) malloc(3C) http://docs.sun.com/app/docs/doc/816-5168/6mbb3hrgp?a=view http://bama.ua.edu/cgi-bin/man-cgi?malloc+3C (注意跟下面那个链接的区别,在bsdmalloc(3MALLOC)与malloc(3MALLOC)之间搞平衡) malloc(3MALLOC) http://docs.sun.com/app/docs/doc/816-5168/6mbb3hrgq?a=view http://bama.ua.edu/cgi-bin/man-cgi?malloc+3MALLOC (介绍了mallopt、M_KEEP、-lmalloc,性能最差但空间利用率高) mapmalloc(3MALLOC) (介绍了-lmapmalloc,实现与手册描述不符,参后文讨论) [3] For Linux Advanced Memory Allocation - Gianluca Insolvibile [2003-05-01] http://www.linuxjournal.com/article/6390 (介绍了mallopt、MALLOC_TRIM_THRESHOLD、malloc_trim、mallinfo) /usr/include/malloc.h (有很多man手册里未提到的细节) "free" does not frees memory ? - Baruch Even [2006-10-03] http://www.mail-archive.com/linux-il@cs.huji.ac.il/msg45577.html Google for "madvise MADV_DONTNEED MADV_FREE" -------------------------------------------------------------------------- 根据[2],虽然Solaris没有像AIX那么善解人意,但至少有了一个可选方案,即调用 malloc(3MALLOC),而不是调用malloc(3C),换句话说,用-lmalloc应该就可以解决 问题。在这个思路下我写了一个测试程序,用-lmalloc链接,最终还用ldd确认;遗 憾的是,在我的一台SPARC/Solaris 9上并没有看到期待中的效果。 不得已,决定有条件、有限度地自己干预一下Solaris的堆管理机制,动用系统调用 sbrk();此时无论是否用-lmalloc链接,第一个memtrimtest进程都不会干挠到第二 个memtrimtest进程。为减少不必要的干挠,请以root身份进行测试。 -------------------------------------------------------------------------- /* * For x86/Linux Kernel 2.6.16.5 * gcc -DLinux -Wall -pipe -O3 -s -o memtrimtest memtrimtest.c * * For SPARC/Solaris 9 * gcc -DSparc -Wall -pipe -O3 -s -o memtrimtest memtrimtest.c * gcc -DSparc -Wall -pipe -O3 -s -o memtrimtest memtrimtest.c -lmalloc * gcc -Wall -pipe -O3 -s -o memtrimtest memtrimtest.c -lmapmalloc * mcs -d memtrimtest */ #include #include #include #include #include /* * for mallopt() */ #include int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; unsigned char **parray = NULL; unsigned int i, xint = 587, yint = 1024 * 1024 * 5, zint = 0; #if defined(Sparc) || defined(Solaris) void *begin, *end; #endif #if 0 fprintf( stderr, "Usage: %s [xint] [yint] [zint]\n", argv[0] ); #endif if ( argc > 1 ) { if ( 0 == ( xint = ( unsigned int )strtoul( argv[1], NULL, 0 ) ) ) { fprintf( stderr, "Checking your \n" ); goto main_exit; } } if ( argc > 2 ) { if ( 0 == ( yint = ( unsigned int )strtoul( argv[2], NULL, 0 ) ) ) { fprintf( stderr, "Checking your \n" ); goto main_exit; } } if ( argc > 3 ) { zint = ( unsigned int )strtoul( argv[3], NULL, 0 ); } #if 0 mallopt( M_KEEP, 0 ); #endif #if defined(Sparc) || defined(Solaris) if ( ( void * )-1 == ( begin = sbrk( 0 ) ) ) { perror( "sbrk for begin" ); goto main_exit; } #endif /* * 刻意使用calloc()而不是malloc() */ if ( NULL == ( parray = ( unsigned char ** )calloc( xint, sizeof( unsigned char * ) ) ) ) { perror( "calloc for parray" ); goto main_exit; } for ( i = 0; i < xint; i++ ) { /* * 刻意使用calloc()而不是malloc() */ if ( NULL == ( parray[i] = ( unsigned char * )calloc( yint, 1 ) ) ) { fprintf ( stderr, "calloc for parray[%u] error: %s\n", i, strerror( errno ) ); goto main_exit; } parray[i][yint-1] = i; } /* end of for */ /* * 产生阻塞 */ fprintf( stderr, "Press any key [0]" ); getchar(); for ( i = 0; i < xint; i++ ) { #if 1 free( parray[i] ); #else realloc( parray[i], 0 ); #endif parray[i] = NULL; } /* end of for */ free( parray ); parray = NULL; #if defined(Sparc) || defined(Solaris) if ( ( void * )-1 == ( end = sbrk( 0 ) ) ) { perror( "sbrk for end" ); goto main_exit; } if ( ( void * )-1 == sbrk( begin - end ) ) { perror( "sbrk for begin minus end" ); goto main_exit; } #endif if ( !zint ) { /* * 产生阻塞 */ fprintf( stderr, "Press any key [1]" ); getchar(); } ret = EXIT_SUCCESS; main_exit: if ( NULL != parray ) { for ( i = 0; i < xint; i++ ) { if ( NULL != parray[i] ) { #if 1 free( parray[i] ); #else realloc( parray[i], 0 ); #endif parray[i] = NULL; } } /* end of for */ free( parray ); parray = NULL; } return( ret ); } /* end of main */ -------------------------------------------------------------------------- 这是在非常特定的前提下动用sbrk()直接干预Solaris堆管理机制,不是正经解决方 案。事实上这样做并不安全,动用sbrk()后,绕过了堆管理结构,使得堆管理结构与 虚拟内存(物理内存+SWAP)之间不再同步,sbrk( begin - end )释放了虚拟内存,但 堆管理结构并不知道这一点,如果后面还有malloc()操作,并且读写其返回的堆区, 很可能出事。本例也就是马上要结束进程了,否则真不敢乱用sbrk( begin - end ) 收缩堆区。 valent提供的信息是AIX上同类问题的通用解决方案,不知Solaris是否有类似的通用 解决方案。至少在这次测试中,-lmalloc无效。 我在x86/Linux Kernel 2.6.16.5上测试的时候,无论是否动用sbrk(),都未出现第 一个memtrimtest进程干挠到第二个memtrimtest进程的现象,这是好事。 A: sanshao@SMTH 2006-10-18 参mapmalloc(3MALLOC),虽然manual里声称: There is no reclaiming of memory. 但实际上源码中每次free()都试图调用一下defrag...(),其中会有munmap()。以前 述memtrimtest.c为例,用-lmapmalloc链接即可解决问题: gcc -Wall -pipe -O3 -s -o memtrimtest memtrimtest.c -lmapmalloc 在SPARC/Solaris 9上测试通过。 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 http://sunsolve.sun.com/pub-cgi/show.pl?target=content/content17 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@nsfocus 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: 如果希望对第二块物理硬盘的分区与第一块物理硬盘一样,考虑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 FAQ ID - 1533 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/1533 Document ID - 1533 http://sunsolve.sun.com/pub-cgi/retrieve.pl?doc=faqs/1533 http://sunsolve.sun.com/pub-cgi/Redir.pl?doc=faqs/1533 http://sunsolve.sun.com/search/document.do?assetkey=1-30-1533-1 对于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@nsfocus 下面是我实验机(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: scz@nsfocus 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: scz@nsfocus 注意,Solaris的slice概念和FreeBSD的slice概念不同,FreeBSD的slice概念就是MS 的partition概念,而Solaris的slice概念类似于MS扩展分区上的逻辑驱动器概念。 5.12 Solaris 10如何禁止自动挂接U盘、光盘、软盘 A: yuhuohu@bbs.chinaunix.net 2008-01-09 00:31 svcadm disable volfs 5.13 重启vold失败 Q: 在SPARC/Solaris 7上插入光盘后发现未能自动mount上来,"ps -ef | grep vold"发 现vold进程不见了,执行"/etc/init.d/volmgt start",在console上报了一个错: NFS server for volume management (/vol) not responding still trying "ls -l /vol"继续报错: NFS server for volume management (/vol) not responding still trying Ctrl-C之后报错: NFS getattr failed for server for volume management (/vol): error 23 (RPC: Unitdata error) A: # grep vol /etc/mnttab SCZSUN7:vold(pid193) /vol nfs ignore,noquota,dev=2c80001 1217391217 如果有输出,尝试如下操作: # umount /vol nfs umount: SCZSUN7:vold(pid193) server not responding: RPC: Program not registered # grep vol /etc/mnttab # /etc/init.d/volmgt start volume management starting. # ps -ef | grep vold root 454 1 0 12:54:01 ? 0:00 /usr/sbin/vold 5.14 给虚拟机里的/dev/sda1扩容 Q: VMware : Workstation 6.0.0 build-45731 VMware Host : 中文Windows 2003 SP2 VMware Guest : Debian 4.0 对于VMware Guest来说,只有一块物理硬盘,/dev/sda1对应根文件系统,/dev/sda2 对应swap。以前这块物理硬盘只给了8GB,现在发现根文件系统不够用了,想无损扩 容。 A: 建议先备份整个VMware Guest。 "X:\Program Files\VMware\VMware Workstation\vmware-vdiskmanager.exe" -x 12Gb "Debian 4.0.vmdk" -x指定的是扩容后的总尺寸,不是增量尺寸。 对于非Workstation版本,请搜索"vmkfstools.exe -X"的用法。 将这个vmdk文件做为数据盘挂到另一个Debian 4.0虚拟机上。执行gparted,针对数 据盘进行扩容,gparted是一个图形化工具。扩容时需要先删除/dev/sdb2,再针对 /dev/sdb1扩容,最后重新创建/dev/sdb2做swap。在Edit菜单里有三种操作: Undo Last Operation Clear All Operations Apply All Operations 如果怀疑操作有误,应及时撤销、回滚。 用扩容后的硬盘启动,执行如下命令确认扩容成功: fdisk -l df -B 1G swapon -s (cat /proc/swaps) 5.15 /etc/fstab与UUID Q: # mount | grep ext3 /dev/disk/by-uuid/a32ac3dd-1695-4185-959c-f4b638453caf on / type ext3 (rw,relatime,errors=remount-ro,barrier=1,data=ordered) # cat /etc/mtab | grep ext3 /dev/disk/by-uuid/a32ac3dd-1695-4185-959c-f4b638453caf / ext3 rw,relatime,errors=remount-ro,barrier=1,data=ordered 0 0 原来fstab中是: /dev/sda1 /dev/sda2 现在变成: UUID=a32ac3dd-1695-4185-959c-f4b638453caf UUID=e61466ef-8561-4338-be98-c3cc946a29ec 为什么不用设备名而用UUID?怎么知道指定设备名对应的UUID? A: 假设主机挂了4块硬盘,分别是sda、sdb、sdc、sdd,现在拔掉sdc,重启主机后sdd变 成sdc。用设备名没法做到重启后sdd还是sdd。 # blkid /dev/sda1: UUID="a32ac3dd-1695-4185-959c-f4b638453caf" TYPE="ext3" /dev/sda2: UUID="e61466ef-8561-4338-be98-c3cc946a29ec" TYPE="swap" # blkid -U a32ac3dd-1695-4185-959c-f4b638453caf /dev/sda1 # blkid /dev/sda2 /dev/sda2: UUID="e61466ef-8561-4338-be98-c3cc946a29ec" TYPE="swap" # findfs UUID=e61466ef-8561-4338-be98-c3cc946a29ec /dev/sda2 # ls -l /dev/disk/by-uuid lrwxrwxrwx 1 root root 10 3月 6 17:50 a32ac3dd-1695-4185-959c-f4b638453caf -> ../../sda1 lrwxrwxrwx 1 root root 10 3月 6 17:47 e61466ef-8561-4338-be98-c3cc946a29ec -> ../../sda2 # dumpe2fs -h /dev/sda1 | grep UUID Filesystem UUID: a32ac3dd-1695-4185-959c-f4b638453caf # tune2fs -l /dev/sda1 | grep UUID Filesystem UUID: a32ac3dd-1695-4185-959c-f4b638453caf Q: 如何修改分区对应的UUID? A: tune2fs -U a32ac3dd-1695-4185-959c-f4b638453caf /dev/sda1 tune2fs -U random /dev/sda1 tune2fs -U time /dev/sda1 tune2fs -U clear /dev/sda1 aptitude install uuid-runtime tune2fs -U $(uuidgen) /dev/sda1 # blkid (获取原来的值) # tune2fs -U $(uuidgen) /dev/sda1 # blkid /dev/sda1 /dev/sda1: UUID="a5faa0a1-0a68-40f1-89b6-cbf5bf3ee3b4" TYPE="ext3" sed -i 's/a32ac3dd-1695-4185-959c-f4b638453caf/a5faa0a1-0a68-40f1-89b6-cbf5bf3ee3b4/g' /etc/fstab sed -i 's/a32ac3dd-1695-4185-959c-f4b638453caf/a5faa0a1-0a68-40f1-89b6-cbf5bf3ee3b4/g' /boot/grub/grub.cfg sed -i 's/a32ac3dd-1695-4185-959c-f4b638453caf/a5faa0a1-0a68-40f1-89b6-cbf5bf3ee3b4/g' /boot/grub/menu.lst 6. 可调资源限制 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.4 Linux如何产生core dump Q: 进程产生了Segmentation fault,却没有产生core dump。 A: ulimit -c unlimited 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 FAQ ID - 3455 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/3455 出于安全考虑,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@nsfocus 见鬼,在SPARC/Solaris 7上测试了上述技术,无效! core文件可能包含系统敏感信息,比如shadow片段。而且core文件通常巨大,容易导 致拒绝服务攻击。如果并不在服务器上调试程序或者根本不知道什么是core文件,考 虑在/etc/system文件中增加 set sys:coredumpsize = 0 参看limit(1)手册页了解更多细节。可我怎么利用adb获取这个内核参数的当前值呢? D: scz@nsfocus 参看《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: scz@nsfocus 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@nsfocus /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@nsfocus 用nslookup是最普遍适用的: nslookup > server ns.tsinghua.edu.cn > ls -d tsinghua.edu.cn [> tsinghua.txt] (方括号里的可选) 有些系统提供了dig命令: dig @ns.tsinghua.edu.cn axfr tsinghua.edu.cn A: lgwu 有些系统提供了host命令,这个命令不太保险: host -l net.tsinghua.edu.cn (后面指定域) host -l ncic.ac.cn 7.2 如何获知权威名字服务器 A: scz@nsfocus nslookup > set query=ns > ncic.ac.cn (获知管辖该域的权威名字服务器) Authoritative answers can be found from: gatekeeper.ncic.ac.cn internet address = 159.226.41.188 > server gatekeeper.ncic.ac.cn > ls -d ncic.ac.cn > ncic.txt 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 可以在nslookup命令行上直接指定DNS Server: nslookup - A: scz nslookup -q=ns 263.net dig @ 263.net ns host -t ns 263.net 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> D: scz@nsfocus 参看RFC 1034的3.6小节,NS记录的RDATA应该用主机名,而不是IP,因此正经子域委 托配置如下: 子域 IN NS <负责子域DNS解析的主机名> <负责子域DNS解析的主机名> IN A <负责子域DNS解析的IP> 但一般直接在NS记录的RDATA中使用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@nsfocus 2002-11-27 16:30 BIND支持如下查询请求,参看src/bin/named/ns_req.c中的req_query()函数 nslookup -q=txt -class=chaos VERSION.BIND nslookup -q=txt -class=chaos HOSTNAME.BIND dig @ txt chaos VERSION.BIND dig @ txt chaos HOSTNAME.BIND host -c chaos -t txt VERSION.BIND host -c chaos -t txt HOSTNAME.BIND A: backend@nsfocus #! /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 "。 如果要美观一点,所以可以用这样一个小脚本: #! /bin/sh if [ $# = 0 ];then echo "useage: $0 "; 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 -------------------------------------------------------------------------- 7.6 什么时候会用到53/TCP Q: 平时用Sniffer观察到的DNS报文都在使用53/UDP,什么时候会用到53/TCP? A: scz 对于DNS服务器,递归解析时用53/UDP,区传输因需要可靠传输,必须使用53/TCP。 DNS服务器的标准实现必须同时支持53/TCP和53/UDP。 对于DNS客户端,默认使用53/UDP进行A记录查询。nslookup进行A记录查询时,可以 强制使用53/TCP。用Sniffer观察下述操作引发的通信报文: nslookup > set vc > www.netexpert.cn RFC 1035中指出,53/UDP上的UDP数据区(不包括UDP首部)不得超过512字节,发送时 如果超过512字节,将被截断成512字节,同时DNS协议Flags字段Truncated位置位。 53/TCP上的数据区最前面是big-endian序的2字节长度域,不包括自身这2字节,指明 了后续数据长度。 更多细节参看"http://www.ietf.org/rfc/rfc1035.txt"。 网上曾经流行过这样一个说法,当DNS服务产生的响应数据大于512字节(指UDP数据区 )时会自动改用53/TCP。注意,RFC 1035从来没有给出过这种说法,即使确有其事那 也是DNS服务实现相关的,不是标准行为。 A: W. Richard Stevens 1994 参看<> 14.8节。 名字解析器(DNS Client或递归解析中的DNS Server)发出UDP请求报文,名字服务器( 必然是DNS Server)产生的UDP响应数据大于512字节时,只会向名字解析器发送前512 字节,同时TC位置位。名字解析器通过TC位得知响应数据没有全部返回,一般实现会 选择用TCP重新请求,名字服务器将用TCP返回完整的响应数据。 7.7 使用53/TCP进行域名解析 A: >nslookup twitter.com 8.8.8.8 Address: 78.16.49.15 >nslookup -vc twitter.com 8.8.8.8 // 使用53/TCP Addresses: 199.59.149.198, 199.59.148.83, 199.59.148.82 >nslookup twitter.com 8.8.4.4 Addresses: 199.59.148.83, 199.59.148.82, 199.59.148.10 >nslookup -vc twitter.com 8.8.4.4 // 使用53/TCP Addresses: 199.59.149.198, 199.59.148.10, 199.59.148.83 $ dig @8.8.8.8 twitter.com +short 46.82.174.68 $ dig @8.8.8.8 twitter.com +short +vc // 使用53/TCP 199.59.148.10 199.59.149.198 199.59.148.82 $ dig @8.8.4.4 twitter.com +short 199.59.148.10 199.59.149.198 199.59.148.82 $ dig @8.8.4.4 twitter.com +short +vc // 使用53/TCP 199.59.148.10 199.59.149.198 199.59.148.82 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.2 Solaris下如何动态增加系统调用 Q: x86/Linux、x86/FreeBSD系统可以利用LLKM以及FKLD动态增加系统调用, SPARC/Solaris系统如何达到同样目的? A: Sun Microsystems 1999-03-30 FAQ ID - 2678 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/2678 D: scz@nsfocus 下面假设在SPARC/Solaris 7 64-bit kernel mode上讨论,增加一个32-bit的系统调 用。参看kernel(1M)手册页。 编辑/etc/name_to_sysnum文件,增加如下行: mySyscall 180 编写可加载系统调用模块mySyscall.c -------------------------------------------------------------------------- /* * Copyright (c) 1999 Sun Microsystems, Inc. * * Modified by scz * * /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -D_KERNEL -O -c mySyscall.c * /usr/ccs/bin/ld -o mySyscall -r mySyscall.o */ #include #include #include #include #include #include #include #include #include #include #include #include #include static int mySyscall32 ( char *, char * ); extern struct mod_ops mod_syscallops32; static struct sysent mods_sysent = { 2, SE_ARGC, ( int ( * ) () )mySyscall32, ( krwlock_t * ) NULL }; static struct modlsys modlsys = { &mod_syscallops32, "Syscall 32-bit Example", &mods_sysent }; static struct modlinkage modlinkage = { MODREV_1, ( void * )&modlsys, 0 }; static int module_keepcnt = 0; int _init ( void ) { return( mod_install( &modlinkage ) ); } int _fini ( void ) { if ( module_keepcnt != 0 ) { set_errno( EBUSY ); return( -1 ); } return( mod_remove( &modlinkage ) ); } int _info ( struct modinfo * modinfop ) { return( mod_info( &modlinkage, modinfop ) ); } static int mySyscall32 ( char * s, char * buf ) { char my_buf[80], rev_buf[80]; int i = 0, j; cred_t *creds = CRED(); if ( suser( creds ) == 0 ) { set_errno( EACCES ); return( -1 ); } if ( copyin( s, my_buf, sizeof( my_buf ) ) ) { set_errno( EFAULT ); return( -1 ); } j = strlen( my_buf ) - 1; strcpy( rev_buf, my_buf ); do { rev_buf[ j ] = my_buf[ i ]; i++, j--; } while ( my_buf[ i ] ); if ( copyout( rev_buf, buf, strlen( rev_buf ) ) ) { set_errno( EFAULT ); return( -1 ); } return( 0 ); } -------------------------------------------------------------------------- 编写可加载系统调用模块测试程序mySyscallTest.c -------------------------------------------------------------------------- /* * Modified by scz * * gcc -o mySyscallTest mySyscallTest.c */ #include #include #define STRING "Hello Syscall - Please reverse me" int main ( int argc, char * argv[] ) { char in_buf[80] = STRING; char out_buf[80] = STRING; if ( ( syscall( 180, in_buf, out_buf ) ) < 0 ) { perror( "Syscall Failed" ); exit( -1 ); } printf( "Input : %s\n", in_buf ); printf( "Result : %s\n", out_buf ); return( 0 ); } -------------------------------------------------------------------------- 注意这种判断 if ( suser( creds ) == 0 ) { set_errno( EACCES ); return( -1 ); } 必须使用set_errno( EACCES ),而不能return( EACCES ),否则达不到效果。从 Solaris 7的源码来看,return( set_errno( EACCES ) )也是可以的。为了演示代码 的清晰,这里少了很多安全检查,如果是正规编程,务必小心谨慎。 如果把这个mySyscall复制到/usr/kernel/sys下,即使没有手动加载(modload),只 要mySyscallTest使用了180号系统调用,系统会自动动态加载mySyscall模块。 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: scz@nsfocus 在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: scz@nsfocus 2002-07-07 15:20 写三个脚本自动清除所有TIME_WAIT状态TCP连接: SPARC/Solaris 8 64-bit kernel mode -------------------------------------------------------------------------- #! /sbin/sh ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \ egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcpb_addr do adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF $tcpb_addr+0x30/Z 0t6 $tcpb_addr+0x40/W -6 \$q NSFOCUS_EOF done -------------------------------------------------------------------------- SPARC/Solaris 7 64-bit kernel mode -------------------------------------------------------------------------- #! /sbin/sh ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \ egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcp_addr do adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF $tcp_addr+0x50/Z 0t6 $tcp_addr+0x58/W -6 \$q NSFOCUS_EOF done -------------------------------------------------------------------------- SPARC/Solaris 2.6 32-bit kernel mode -------------------------------------------------------------------------- #! /sbin/sh /usr/sbin/ndd /dev/tcp tcp_status | nawk '{print $1 " " $2 " " $16 $17 " " $18}' | \ egrep 'TIME_WAIT' | cut -d' ' -f1 | while read tcp_addr do adb -kw /dev/ksyms /dev/mem << NSFOCUS_EOF $tcp_addr+0x28/W 0t6 $tcp_addr+0x2c/W -6 \$q NSFOCUS_EOF done -------------------------------------------------------------------------- 8.4 结构在优化编译中的对齐问题 Q: 什么是结构在优化编译中的对齐问题 A: scz@nsfocus 看这样两个结构定义 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-bits编译,则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_*()函数,几乎没有兼容性、可移植性可言。 A: scz@nsfocus 原代码有误,应该使用/dev/mem作为corefile,而不是/dev/kmem,上例已经做了 修改。编译命令如下: Solaris 2.6/7 32-bit gcc -O3 -o getshm getshm.c -lkvm Solaris 7 64-bit /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o getshm getshm.c -lkvm 这个程序只能以root身份运行,应用受限。 顺便我们介绍Mark Henderson提供的sethostid.c,该代码用于修改Solaris 2.6/7系 统上的hostid,如果企图用于Solaris 7,必须用Workshop 5.0编译成64-bit应用程 序,否则无法成功。很多朋友抱怨过change-sun-hostid.tar.gz里的代码无法用于7, 正是这个毛病,我已经增加了64-bit编译注释。 -------------------------------------------------------------------------- /* * Mark Henderson < mailto: mch@squirrel.com > * * 必须以root身份运行,至少拥有/dev/kmem的读写权限 * * Solaris 2.6 32-bit * gcc -O3 -o sethostid sethostid.c -lelf * * Solaris 2.7 64-bit * /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o sethostid sethostid.c -lelf * * 不带参数运行直接返回当前hw_serial * 带参数运行则设置内存映像hw_serial,命令行参数采用16进制指定 * * June 1996 */ #include #include #include #include #include int main ( int argc, char * argv[] ) { struct nlist nl[2]; int kmem; off_t where; /* * scz: 这里必须避免使用u_long,64-bit问题 */ unsigned int new_hostid; u_char hw_serial[12]; u_char new_hw_serial[12]; if ( ( kmem = open( "/dev/kmem", O_RDWR ) ) < 0 ) { fprintf( stderr, "cannot open /dev/kmem\n" ); exit( 1 ); } nl[0].n_name = "hw_serial"; nl[1].n_name = NULL; if ( nlist( "/dev/ksyms", nl ) < 0 ) { fprintf( stderr, "cannot read namelist out of /dev/ksyms\n" ); exit( 1 ); } if ( ( where = nl[0].n_value ) == 0 ) { fprintf( stderr, "unknown kernel variable hw_serial\n" ); exit( 1 ); } if ( lseek( kmem, where, SEEK_SET ) == -1 ) { fprintf( stderr, "lseek on /dev/kmem failed\n" ); exit( 1 ); } if ( read( kmem, ( char * )&hw_serial[0], 12 ) < 12 ) { fprintf( stderr, "read from /dev/kmem failed\n" ); exit( 1 ); } if ( lseek( kmem, where, SEEK_SET ) == -1 ) { fprintf( stderr, "lseek on /dev/kmem failed\n" ); exit( 1 ); } fprintf( stderr, "current hostid is 0x%08x\n", strtoul( hw_serial, NULL, 10 ) ); if ( argc > 1 ) { if ( lseek( kmem, where, SEEK_SET ) == -1 ) { fprintf( stderr, "lseek on /dev/kmem failed\n" ); exit( 1 ); } new_hostid = strtoul( argv[1], NULL, 16 ); fprintf( stderr, "setting hostid to 0x%08x\n", new_hostid ); sprintf( ( char * )&new_hw_serial, "%u", new_hostid ); if ( write( kmem, ( char * )&new_hw_serial[0], strlen( new_hw_serial ) + 1 ) < strlen(new_hw_serial) + 1 ) { fprintf( stderr, "write to /dev/kmem failed\n" ); exit( 1 ); } } close( kmem ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- 因为很多朋友没有64-bit编译器,这里提供一个64-bit的sethostid -------------------------------------------------------------------------- Content-Type: text/plain; charset=US-ASCII; name=sethostid Content-transfer-encoding: base64 f0VMRgICAQAAAAAAAAAAAAACACsAAAABAAAAAQAADSAAAAAAAAAAQAAAAAAA ABkYAAAAAABAADgABQBAABcAFgAAAAYAAAAFAAAAAAAAAEAAAAABAAAAQAAA AAAAAAAAAAAAAAAAARgAAAAAAAABGAAAAAAAAAAAAAAAAwAAAAQAAAAAAAAB WAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAAAAAAAAAAAAAAAAAAAAAAAAB AAAABQAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAABO7AAAAAAAAE7sA AAAAABAAAAAAAAEAAAAHAAAAAAAAE8AAAAABABATwAAAAAAAAAAAAAAAAAAA BGIAAAAAAAAOcAAAAAAAEAAAAAAAAgAAAAcAAAAAAAAWCAAAAAEAEBYIAAAA AAAAAAAAAAAAAAABkAAAAAAAAAAAAAAAAAAAAAAvdXNyL2xpYi9zcGFyY3Y5 L2xkLnNvLjEAAAAAAAAAAAAAADUAAABBAAAAQAAAAAAAAAAAAAAANQAAAAAA AAAnAAAAKQAAACEAAAA6AAAALwAAAAAAAAAAAAAALgAAAAAAAAAAAAAAMAAA ADkAAAAAAAAAAAAAADwAAAAAAAAALAAAAD4AAAAAAAAAAAAAADgAAAAAAAAA KwAAAAAAAAAAAAAAKAAAAD8AAAAtAAAAAAAAADMAAAA3AAAAAAAAAAAAAAAx AAAAHwAAADIAAAAAAAAAIgAAAAAAAAAjAAAAOwAAAAAAAAAAAAAAAAAAAAAA AAAqAAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAACUAAAAAAAAAAAAA AAAAAAAeAAAANgAAAAAAAAAmAAAAAAAAAAAAAAAAAAAANAAAACQAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAABAAAAAQAAAVgAAAAA AAAAAAAAAAADAAACAAAAAQAAAXgAAAAAAAAAAAAAAAADAAADAAAAAQAAA1gA AAAAAAAAAAAAAAADAAAEAAAAAQAACXAAAAAAAAAAAAAAAAADAAAFAAAAAQAA CxgAAAAAAAAAAAAAAAADAAAGAAAAAQAAC1gAAAAAAAAAAAAAAAADAAAHAAAA AQAAC7gAAAAAAAAAAAAAAAADAAAIAAAAAQAAC9AAAAAAAAAAAAAAAAADAAAJ AAAAAQAADSAAAAAAAAAAAAAAAAADAAAKAAAAAQAAEZAAAAAAAAAAAAAAAAAD AAALAAAAAQAAEegAAAAAAAAAAAAAAAADAAAMAAAAAQAAEkAAAAAAAAAAAAAA AAADAAANAAAAAQAAEkgAAAAAAAAAAAAAAAADAAAOAAAAAQAAElAAAAAAAAAA AAAAAAADAAAPAAAAAQAQE8AAAAAAAAAAAAAAAAADAAAQAAAAAQAQE+AAAAAA AAAAAAAAAAADAAARAAAAAQAQFggAAAAAAAAAAAAAAAADAAASAAAAAQAQF5gA AAAAAAAAAAAAAAADAAATAAAAAQAQF+gAAAAAAAAAAAAAAAADAAAUAAAAAQAQ GBgAAAAAAAAAAAAAAAADAAAVAAAAAQAQGDAAAAAAAAAAAAAAAAADAAAWAAAA AAAAAAAAAAAAAAAAAAAAAAADAAAXAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAY AAAAAAAAAAAAAAAAAAAAAAAAAAADAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAD AAAaAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAbAAAAAAAAAAAAAAAAAAAAAAAA AAAdAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAdAAAAAAAAAAAAAAMAAAAAAAAA AAAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4SAAAJAAAAAQAADSAAAAAA AAABBAAAACUSAAAAAAAAAQAQFSAAAAAAAAAAAAAAACoSAAAAAAAAAQAQFUAA AAAAAAAAAAAAADIRAAATAAAAAQAQF+gAAAAAAAAACAAAADsRAAAVAAAAAQAQ IjAAAAAAAAAAAAAAAEAhAAAVAAAAAQAQGDAAAAAAAAAKAAAAAEURAAAPAAAA AQAQE8AAAAAAAAAAAAAAAFsSAAAAAAAAAQAQFGAAAAAAAAAAAAAAAGISAAAA AAAAAQAQFIAAAAAAAAAAAAAAAGcSAAAAAAAAAQAQFYAAAAAAAAAAAAAAAG8S AAAKAAAAAQAAEZgAAAAAAAAAVAAAAHUgAAAAAAAAAAAAAAAAAAAAAAAAAAAA AIQRAAATAAAAAQAQGBAAAAAAAAAACAAAAIwRAAARAAAAAQAQFggAAAAAAAAA AAAAAJURAAAVAAAAAQAQGDAAAAAAAAAKAAAAAJsSAAAAAAAAAQAQFcAAAAAA AAAAAAAAAKEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAALISAAAAAAAAAQAQFKAA AAAAAAAAAAAAALghAAATAAAAAQAQF+gAAAAAAAAACAAAAMASAAAAAAAAAQAQ FOAAAAAAAAAAAAAAAMYSAAAAAAAAAQAQFeAAAAAAAAAAAAAAAMwSAAAAAAAA AQAQFMAAAAAAAAAAAAAAANERAAAUAAAAAQAQGCIAAAAAAAAAAAAAANgRAAAQ AAAAAQAQE+AAAAAAAAAAAAAAAPIRAP/xAAAAAAAAAAAAAAAAAAAAAAAAAQMR AAAOAAAAAQAAE7sAAAAAAAAAAAAAAQoRAAANAAAAAQAAEkgAAAAAAAAABAAA ARcSAAAJAAAAAQAADigAAAAAAAADaAAAARwSAAAAAAAAAQAQFaAAAAAAAAAA AAAAASMRAAATAAAAAQAQF/AAAAAAAAAAGAAAATISAAAAAAAAAQAQFQAAAAAA AAAAAAAAATgSAAALAAAAAQAAEfAAAAAAAAAAWAAAAT4gAAAAAAAAAAAAAAAA AAAAAAAAAAAAAVsSAAAAAAAAAQAQFWAAAAAAAAAAAABfXzFjSF9fQ2ltcGxL Y3BsdXNfZmluaTZGX3ZfAF9zdGFydAByZWFkAHN0cnRvdWwAX2Vudmlyb24A X2VuZABfaW9iAF9HTE9CQUxfT0ZGU0VUX1RBQkxFXwBhdGV4aXQAZXhpdABz cHJpbnRmAF9pbml0AF9leF9yZWdpc3RlcjY0AF9fX0FyZ3YAX0RZTkFNSUMA X19pb2IAd3JpdGUAX2V4X2RlcmVnaXN0ZXI2NABfZXhpdABlbnZpcm9uAG5s aXN0AGNsb3NlAG9wZW4AX2VkYXRhAF9QUk9DRURVUkVfTElOS0FHRV9UQUJM RV8AX19mc3JfaW5pdF92YWx1ZQBfZXRleHQAX2xpYl92ZXJzaW9uAG1haW4A c3RybGVuAF9fZW52aXJvbl9sb2NrAGxzZWVrAF9maW5pAF9fMWNIX19DaW1w bEtjcGx1c19pbml0NkZfdl8AZnByaW50ZgBsaWJlbGYuc28uMQBTVU5XXzAu NwBsaWJjLnNvLjEAU1VOV18wLjcAbGliZWxmLnNvLjEAbGliYy5zby4xAAAA AAAAAAAAAQABAAABYwAAABAAAAAgCj0olwAAAAAAAAFvAAAAAAABAAEAAAF4 AAAAEAAAAAAKPSiXAAAAAAAAAYIAAAAAAAAAAQAQF6gAAAAqAAAAIAAAAAAA AAAAAAAAAQAQF7AAAAAvAAAAIAAAAAAAAAAAAAAAAQAQF6AAAAAeAAAAIAAA AAAAAAAAAAAAAQAQF5gAAAA/AAAAIAAAAAAAAAAAAAAAAQAQGDAAAAAtAAAA EwAAAAAAAAAAAAAAAQAQFGAAAAAmAAAAFQAAAAAAAAAAAAAAAQAQFIAAAAAn AAAAFQAAAAAAAAAAAAAAAQAQFKAAAAAwAAAAFQAAAAAAAAAAAAAAAQAQFMAA AAA0AAAAFQAAAAAAAAAAAAAAAQAQFOAAAAAyAAAAFQAAAAAAAAAAAAAAAQAQ FQAAAAA9AAAAFQAAAAAAAAAAAAAAAQAQFSAAAAAgAAAAFQAAAAAAAAAAAAAA AQAQFUAAAAAhAAAAFQAAAAAAAAAAAAAAAQAQFWAAAABAAAAAFQAAAAAAAAAA AAAAAQAQFYAAAAAoAAAAFQAAAAAAAAAAAAAAAQAQFaAAAAA7AAAAFQAAAAAA AAAAAAAAAQAQFcAAAAAuAAAAFQAAAAAAAAAAAAAAAQAQFeAAAAAzAAAAFQAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvBAgAOBbqH+iA6iHGwAA AJoTYAGbK3AgEwAEBpISQA3icmAQpSwgA6QEoAikBEASGwAAAJoTYAGbK3Ag JwAEBaYUwA3kdOPoKwAAAKoVYACAkAAVAoAAFQEAAAAbAAAAmhNgAZsrcCAp AAQGqBUADa0tYALBLSAIrg2jAKwNYD+sFYAXqy2gFuwFIAiuIAAXrz3gH68t 4B6sLYAXrBWAFewlIAjBDSAInCOgMICQAAECgAAEAQAAAEAEAaKQEAABGwAA AJoTYAGbK3AgEQAABJASAA1ABAGbkBIh8EAAAOcBAAAAkBAAEJIQABFAAAAH lBAAEkAEAZsBAAAAQAQBoQEAAAAAAAAABQAAAAcABAaEEKABhhDhMIUosCCd 478AtBDAAgUAAAAHAAAEhBCgAYUosCCGEOJQkBDAArgQABhABAGYkhAgArYQ ABmwkAAIBkAAagUAAAAHAAQGhBCgAYUosCCGEOAYhBDAAsR3p88HAAAEBQAA AMB3p+eGEOJ4hBCgAYUosCCQEMACQAQBjZIHp8+AkAAIBkAAZPJfp9eApmAA AmAAbZAQABqxPiAAkBAAGJIQABlABAGKlBAgAICiP/8CYABwlBAgDJAQABhA BAGMkgenw4CiIAwGYAB2kBAAGJIQABlABAF+lBAgAICiP/8CYAB8khAgAJAH p8NABAGIlBAgCpU6IAAFAAAABwAABIQQoAGFKLAgkBAAGoYQ4zhABAGHkhDA AoCnIAEESAAukBAAGJIQABlABAFplBAgAICiP/8CYABzkhAgANBe4AhABAFz lBAgEAUAAAAHAAAEhBCgAZQQAAiFKLAghhDjeKEyoACSEMACkBAAGkAEAXCV MqAABQAAAAcAAASEEKABhhDjmIUosCCQB6e3khDAAkAEAW+UEAAQQAQBdZAH p7eEAiABoTigAEAEAXGQB6e3hAIgAZAQABiVOKAAQAQBdJIHp7eAogAQBmAA WpAQABqQEAAYQAQBdgEAAACBx+AIkeggAAUAAAAHAAAEhBCgAZAQABqFKLAg hhDiYEAEAUySEMACQAQBEpAQIAEQv/+OBQAAAAUAAAAHAAAEhBCgAZAQABqF KLAghhDiiEAEAUCSEMACQAQBBpAQIAEQv/+U8l+n1wUAAAAHAAAEhBCgAYYQ 4rCFKLAgQAQBNZIQwAJABAD7kBAgARC//42xPiAABQAAAAcAAASEEKABkBAA GoUosCCGEOLYQAQBKZIQwAJABADvkBAgARC//4iUECAMBQAAAAcAAASEEKAB kBAAGoUosCCGEOL4QAQBHZIQwAJABADjkBAgARC//4KQEAAYBQAAAAcAAASE EKABkBAAGoUosCCGEOMYQAQBEZIQwAJABADXkBAgARC//3ySECAABQAAAAcA AASEEKABkBAAGoUosCCGEONYQAQBBZIQwAJABADLkBAgARC//4WSECAABQAA AAcAAASEEKABhhDjoIUosCBABAD6khDAAkAEAMCQECABEL//oJAQABgAAAAA ABAGHJ3jv1BAAAACAQAAANBb//SQA8AI4Fo/8OJaP+CApAAAAoAABAEAAACf xAAAAQAAAICkQAACgAAEAQAAAJ/EQAABAAAAgcfgCIHoAAAAAAAAAAAAAAAQ BcSd479QQAAAAgEAAADQW//0rAPACO5dv/iQEAAWgKXAAAKAAAQBAAAAn8XA AAEAAADgXb/ogKQAAAKAAAQBAAAAn8QAAAEAAACBx+AIgegAAAAAAAAAAAAA AAAAAQAAAAAvZGV2L2ttZW0AAAAAAAAAY2Fubm90IG9wZW4gL2Rldi9rbWVt CgAAL2Rldi9rc3ltcwAAAAAAAGNhbm5vdCByZWFkIG5hbWVsaXN0IG91dCBv ZiAvZGV2L2tzeW1zCgB1bmtub3duIGtlcm5lbCB2YXJpYWJsZSBod19zZXJp YWwKAAAAAAAAbHNlZWsgb24gL2Rldi9rbWVtIGZhaWxlZAoAAAAAAAByZWFk IGZyb20gL2Rldi9rbWVtIGZhaWxlZAoAAAAAAGxzZWVrIG9uIC9kZXYva21l bSBmYWlsZWQKAAAAAAAAY3VycmVudCBob3N0aWQgaXMgMHglMDh4CgAAAAAA AABsc2VlayBvbiAvZGV2L2ttZW0gZmFpbGVkCgAAAAAAAHNldHRpbmcgaG9z dGlkIHRvIDB4JTA4eAoAAAAAAAAAJXUAAAAAAAB3cml0ZSB0byAvZGV2L2tt ZW0gZmFpbGVkCgAAAAAAAAAAAAEAEBYIAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAACA MG//5wEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAMAAKAwb//fAQAAAAEAAAAB AAAAAQAAAAEAAAABAAAAAwAAwDBv/9cBAAAAAQAAAAEAAAABAAAAAQAAAAEA AAADAADgMG//zwEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAMAAQAwb//HAQAA AAEAAAABAAAAAQAAAAEAAAABAAAAAwABIDBv/78BAAAAAQAAAAEAAAABAAAA AQAAAAEAAAADAAFAMG//twEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAMAAWAw b/+vAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAwABgDBv/6cBAAAAAQAAAAEA AAABAAAAAQAAAAEAAAADAAGgMG//nwEAAAABAAAAAQAAAAEAAAABAAAAAQAA AAMAAcAwb/+XAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAwAB4DBv/48BAAAA AQAAAAEAAAABAAAAAQAAAAEAAAADAAIAMG//hwEAAAABAAAAAQAAAAEAAAAB AAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAABiwAAAAAAAAABAAAAAAAA AZcAAAAAAAAADAAAAAEAABGYAAAAAAAAAA0AAAABAAAR8AAAAAAAAAAEAAAA AQAAAXgAAAAAAAAABQAAAAEAAAlwAAAAAAAAAAoAAAAAAAABowAAAAAAAAAG AAAAAQAAA1gAAAAAAAAACwAAAAAAAAAYAAAAAG///fgAAAAAAAC4WAAAAABv ///+AAAAAQAACxgAAAAAb////wAAAAAAAAACAAAAAAAAAAIAAAAAAAABOAAA AAAAAAAUAAAAAAAAAAcAAAAAAAAAFwAAAAEAAAvQAAAAAAAAAAcAAAABAAAL WAAAAAAAAAAIAAAAAAAAAbAAAAAAAAAACQAAAAAAAAAYAAAAAHAAAAEAAAAA AAAAHAAAAABwAAABAAAAAAAAAB0AAAAAAAAAFQAAAAAAAAAAAAAAAG///fwA AAAAAAAAAQAAAABv///7AAAAAAAAAAAAAAAAAAAAAwAAAAEAEBPgAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAP//////7/Vo///////v+oj//////+/52P//////7/qI AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAaHdfc2VyaWFsAAAuaW50ZXJwAC5oYXNoAC5keW5zeW0ALmR5bnN0cgAu U1VOV192ZXJzaW9uAC5yZWxhLmV4X3NoYXJlZAAucmVsYS5ic3MALnJlbGEu cGx0AC50ZXh0AC5pbml0AC5maW5pAC5leGNlcHRpb25fcmFuZ2VzAC5yb2Rh dGEALnJvZGF0YTEALmdvdAAucGx0AC5keW5hbWljAC5leF9zaGFyZWQALmRh dGEALmRhdGExAC5ic3MALnN5bXRhYgAuc3RydGFiAC5zdGFiLmluZGV4AC5j b21tZW50AC5zaHN0cnRhYgAuc3RhYi5pbmRleHN0cgAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAABAAAAAQAAAAAAAAACAAAAAQAAAVgAAAAAAAABWAAAAAAA AAAZAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAACQAAAAUAAAAAAAAAAgAA AAEAAAF4AAAAAAAAAXgAAAAAAAAB4AAAAAMAAAAAAAAAAAAAAAgAAAAAAAAA BAAAAA8AAAALAAAAAAAAAAIAAAABAAADWAAAAAAAAANYAAAAAAAABhgAAAAE AAAAHAAAAAAAAAAIAAAAAAAAABgAAAAXAAAAAwAAAAAAAAACAAAAAQAACXAA AAAAAAAJcAAAAAAAAAGjAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAH2// //4AAAAAAAAAAgAAAAEAAAsYAAAAAAAACxgAAAAAAAAAQAAAAAQAAAACAAAA AAAAAAgAAAAAAAAAAAAAAC0AAAAEAAAAAAAAAAIAAAABAAALWAAAAAAAAAtY AAAAAAAAAGAAAAADAAAAEgAAAAAAAAAIAAAAAAAAABgAAAA9AAAABAAAAAAA AAACAAAAAQAAC7gAAAAAAAALuAAAAAAAAAAYAAAAAwAAABUAAAAAAAAACAAA AAAAAAAYAAAARwAAAAQAAAAAAAAAAgAAAAEAAAvQAAAAAAAAC9AAAAAAAAAB OAAAAAMAAAAQAAAAAAAAAAgAAAAAAAAAGAAAAFEAAAABAAAAAAAAAAYAAAAB AAANIAAAAAAAAA0gAAAAAAAABHAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA AABXAAAAAQAAAAAAAAAGAAAAAQAAEZAAAAAAAAARkAAAAAAAAABUAAAAAAAA AAAAAAAAAAAACAAAAAAAAAAAAAAAXQAAAAEAAAAAAAAABgAAAAEAABHoAAAA AAAAEegAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAGMAAAAB AAAAAAAAAAIAAAABAAASQAAAAAAAABJAAAAAAAAAAAgAAAAAAAAAAAAAAAAA AAAIAAAAAAAAAAAAAAB1AAAAAQAAAAAAAAACAAAAAQAAEkgAAAAAAAASSAAA AAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAfQAAAAEAAAAAAAAA AgAAAAEAABJQAAAAAAAAElAAAAAAAAABawAAAAAAAAAAAAAAAAAAAAgAAAAA AAAAAAAAAIYAAAABAAAAAAAAAAMAAAABABATwAAAAAAAABPAAAAAAAAAAAgA AAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAACLAAAAAQAAAAAAAAAHAAAAAQAQ E+AAAAAAAAAT4AAAAAAAAAIkAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAA kAAAAAYAAAAAAAAAAwAAAAEAEBYIAAAAAAAAFggAAAAAAAABkAAAAAQAAAAA AAAAAAAAAAgAAAAAAAAAEAAAAJkAAAABAAAAAAAAAAMAAAABABAXmAAAAAAA ABeYAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAACkAAAAAQAA AAAAAAADAAAAAQAQF+gAAAAAAAAX6AAAAAAAAAAwAAAAAAAAAAAAAAAAAAAA CAAAAAAAAAAAAAAAqgAAAAEAAAAAAAAAAwAAAAEAEBgYAAAAAAAAGBgAAAAA AAAACgAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAALEAAAAIAAAAAAAAAAMA AAABABAYMAAAAAAAABgwAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAA AAAAAADbAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYIgAAAAAAAAD0AAAA AAAAAAAAAAAAAAAAAQAAAAAAAAAA -------------------------------------------------------------------------- 下面我们用kvm_*()改写一下上述代码,做个对比。 -------------------------------------------------------------------------- /* * flamez - Mark Henderson < mailto: mch@squirrel.com > * greetz - < mailto: hume.spamfilter@bofh.halifax.ns.ca > * creditz - * * scz < mailto: scz@nsfocus.com > * * 必须以root身份运行,至少拥有/dev/kmem的读写权限 * * Solaris 2.6 32-bit * gcc -O3 -o sethostid sethostid.c -lkvm * * Solaris 2.7 64-bit * /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o sethostid sethostid.c -lkvm * * 不带参数运行直接返回当前hw_serial * 带参数运行则设置内存映像hw_serial,命令行参数采用16进制指定 * * Feb 2000 */ #include #include #include #include #include #include int main ( int argc, char * argv[] ) { kvm_t * kmem = NULL; struct nlist nl[2]; u_char hw_serial[12]; u_char new_hw_serial[12]; unsigned int new_hostid; /* scz: 这里必须避免使用u_long,64-bit问题 */ if ( ( kmem = kvm_open( "/dev/ksyms", "/dev/mem", NULL, O_RDWR, argv[0] ) ) == NULL ) { exit( -1 ); } nl[0].n_name = "hw_serial"; nl[0].n_value = 0; nl[0].n_type = 0; nl[1].n_name = NULL; nl[1].n_value = 0; nl[1].n_type = 0; if ( kvm_nlist( kmem, nl ) == 0 ) { if ( nl[0].n_type != 0 ) { if ( kvm_read( kmem, nl[0].n_value, ( char * )&hw_serial, sizeof( hw_serial ) ) != sizeof( hw_serial ) ) { fprintf( stderr, "Unable to fetch hw_serial.\n" ); } else { fprintf( stdout, "current hostid is 0x%08x\n", strtoul( &hw_serial, NULL, 10 ) ); if ( argc > 1 ) { new_hostid = strtoul( argv[1], NULL, 16 ); fprintf( stderr, "setting hostid to 0x%08x\n", new_hostid ); sprintf( ( char * )&new_hw_serial, "%u", new_hostid ); if ( kvm_write( kmem, nl[0].n_value, ( char * )&new_hw_serial, strlen( new_hw_serial ) + 1 ) != strlen( new_hw_serial ) + 1 ) { fprintf( stderr, "write to /dev/kmem failed\n" ); } } } } else { fprintf( stderr, "Unable to fetch hw_serial.\n" ); } } kvm_close( kmem ); exit( 0 ); } /* end of main */ -------------------------------------------------------------------------- 个人不推荐在大型应用软件中使用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.7 内核可加载模块引用了无法解析的符号 Q: 我想创建一个内核模块,该模块将全局输出它的函数,期望能够从其他内核模块 中直接引用这些全局输出了的函数。为了达到这个效果,我在一个头文件中声明 这些函数为extern,第一个内核模块和其他试图访问全局输出函数的内核模块都 会包含这个头文件。 当一个新的内核模块被加载时,内核运行时链接器将解析符号、增加符号信息到 "内核全局链接映射表",以便其他模块直接引用这些全局符号。 我能无误编译两个内核模块,并成功加载第一个模块,它会输出函数,第二个模 块将直接引用这些函数。但是当我试图加载第二个模块的时候,报告"无法解析符 号... ...",参看如下示例: # nm -g /dev/ksyms | fgrep mem_alloc <-- 此时无输出(没有这个符号) # modload ./core <-- 加载第一个模块 # nm -g /dev/ksyms | fgrep mem_alloc <-- ./core输出了mem_alloc函数 [13185] |1518157880| 132|FUNC |GLOB |0 |ABS |mem_alloc # modload ./driver <-- 加载第二个模块失败 ./driver: undefined symbol mem_alloc <-- 无法解析符号mem_alloc 正如你所看到的,加载core内核模块之后,内核符号表中有mem_alloc符号,但是 当driver内核模块企图引用该符号时,报告无法解析。 可同样的,内核输出了kmem_alloc(),为什么所有可加载内核模块都可以正常引 用它呢?请问我该如何做,才能达到预期目的。 A: sini 在你的"driver"代码中,增加 char _depends_on[] = "core"; D: 我写了一个网络设备驱动程序,它依赖于"drv/ip"(为了支持ndd功能)以及另外一 个驱动模块。在网络设备驱动程序中可以定义: static char _depends_on = "drv/ip"; 或者链接时使用: ld -Ndrv/ip 问题是我还需要指定所依赖的另外一个模块。ld的man手册里有如下内容: -N string "This option causes a DT_NEEDED entry to be added to the .dynamic section of the object being built. The value of the DT_NEEDED string will be the string specified on the command line. This option is position dependent, and the DT_NEEDED .dynamic entry will be relative to the other dynamic dependencies discovered on the link-edit line." 似乎可以多次指定"-N string",但是我没看到man手册中有这样的例子,或许我 该使用一个map文件。想必add_drv过程中解析动态依赖关系要比用户空间动态链 接过程更多限制。 A: shridhara 你应该尝试指定多个"-N",以解决这种多依赖问题,man手册里虽然没有 明确指出这样可行,但我想应该是这样的。试试? D: 我试过了,还是无法解决。如果所依赖的第二个驱动模块之前并未加载,即使此 刻指定多个"-N",也不会加载第二个驱动模块(第一个如果之前没有加载, 此时会被自动加载)。后来我到另外一台SUN工作站,在那上面已经加载了所依赖 的第二个驱动模块,此时指定多个"-N",成功加载了自己写的网络设备 驱动程序。 D: scz 某模块已经被加载,并不意味着后续模块可以自动直接引用前者的输出符号,要 么定义_depends_on,要么在链接过程中指定"-N"。 在<<[805-3024] Solaris设备驱动程序编程指南>>的第16章<<调试>>中有如下提 示: Note - _depends_on must not be declared a static variable; if it is, the compiler might optimize it out of the device driver code. 那么前面 static char _depends_on = "drv/ip"; 这种写法就有点问题了。此外,这里写错了吧,至少应该是字符指针类型 char * _depends_on = "drv/ip"; D: 我写了一个伪设备驱动程序和一个内核模块,前者调用了后者输出的一个函数。 在前者里声明了 char _depends_on[] = "misc/mymodule"; 当我使用add_drv加载驱动程序的时候,报告驱动程序被加载但是无法关联。检查 /var/adm/messages,说是驱动里调用的那个模块函数未定义。 D: ret-a 根据<>,_depends_on仅用于scsi drivers。你尝试过 ld的-N选项没有? ld -r -o my_drv ./my_add.o ./my_drv.o -N drv/foo -dy D: 我试过-N和-dy了,错误信息依旧。此外,我不认为_depends_on局限于scsi drivers,当然我也不很确定。没有足够的关于_depends_on的信息,谁有? 解决了,我是如此愚蠢,在内核模块中没有将企图输出的函数声明为全局有效, 定义成静态(static)的了。 A: jacunning 可以在driverB中调用driverA中的函数,只需在driverB中声明: char _depends_on[] = "drv/driverA"; 假设driverA在/kernel/drv目录下,上述声明将迫使加载driverB之前先加载 driverA,然后在driverB中就可以直接调用driverA中的全局函数。 注意,这种分层驱动技术是非公开的、未文档化的,也非DDI/DKI兼容的。 Q: 如何才能在一个驱动程序中调用另外一个驱动程序的例程 A: ret-a 参看sys/conf.h文件中定义的cdev_*()函数,可能对你有用,当然这非DDI/DKI兼 容的。 A: MathewsR ld -r -o xx xx1.o xx2.o -N misc/foo 意思是必须在xx模块加载之前先加载misc/foo,xx模块可以直接调用misc/foo中 的函数。 参看http://docs.sun.com/ab2/coll.45.13/DRIVER/@Ab2PageView/21093 8.9 如何获取Solaris内核可调参数列表 Q: 谁有Solaris内核可调参数列表 A: Andrew Garman 执行 /usr/xpg4/bin/nm /platform/sun4u/kernel/unix | egrep 'OBJT \|GLOB' | more 显示结果中部分为Solaris内核可调参数,另外一些非可调内核参数。可以用ndd获取、 设置网络相关参数。 D: scz@nsfocus 可以考虑 /usr/ccs/bin/nm -nx /dev/ksyms | egrep 'OBJT \|GLOB' | more 不知道二者区别何在?第二个报告内容应该包含了后来动态加载内核模块输出的符号, 第一个才对应基本内核输出的符号。 8.10 如何获取自Unix纪元以来的秒数,如何转换成可理解的表达方式 A: scz@nsfocus 问题是针对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: scz@nsfocus 顺便记录一下各进制之间的转换 $ 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)的可移植性足够好,不过"分配超过请求大小的内存并调整之"可能更具有可 移植性。 D: scz@nsfocus 根据Andrew Gierth的回答,写一个测试程序,利用mmap()完成页边界对齐式内存分 配,不使用MAP_ANON,而是打开/dev/zero。 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o align_malloc align_malloc.c */ #include #include /* * for memcpy */ #include #include #include #include #include #include #include /* * 在SPARC/Solaris 2.6上,这是一页的大小 */ #define BUFSIZE 8192 int main ( int argc, char * argv[] ) { int fd; unsigned char *mem; /* * /dev/zero是666权限 */ if ( ( fd = open( "/dev/zero", O_RDWR ) ) < 0 ) { perror( "open /dev/zero" ); exit( EXIT_FAILURE ); } mem = mmap( 0, BUFSIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE, fd, 0 ); if ( MAP_FAILED == mem ) { perror( "mmap /dev/zero" ); exit( EXIT_FAILURE ); } close( fd ); fprintf( stderr, "mem = %#x\n", ( unsigned int )mem ); strcpy( mem, "NSFocus Security Team.\n" ); /* * 应该是'a' */ fprintf( stderr, "mem[19] = %c\n", mem[19] ); if ( munmap( mem, BUFSIZE ) < 0 ) { perror( "munmap /dev/zero" ); exit( EXIT_FAILURE ); } return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 这个程序的可移植性足够好,并不依赖系统对MAP_ANON的支持。 8.12 Solaris下究竟如何使用setuid/seteuid/setreuid Q: 我被setuid/seteuid/setreuid搞疯了,到底怎么使用它们? A: 下面是Solaris 7的setuid(2)手册页 -------------------------------------------------------------------------- 系统调用 setuid(2) 名字 setuid、seteuid、setgid、setegid - 设置UID和GID 摘要 #include #include int setuid ( uid_t uid ); int seteuid ( uid_t euid ); int setgid ( gid_t gid ); int setegid ( gid_t egid ); 描述 seteuid()只设置EUID。如果当前EUID为0,形参euid任意指定。否则形参euid 应该是RUID、EUID、SUID之一。无论如何,最终只影响当前EUID。 setegid()只设置EGID。如果当前EUID为0,形参egid任意指定。否则形参egid 应该是RGID、EGID、SGID之一。无论如何,最终只影响当前EGID。 登录时,RUID、EUID、SUID设置成登录ID。 进程调用exec(2)执行一个程序文件,考虑两种情况: a. 程序文件set-user-id,则相应进程EUID被设置成这个程序文件的属主ID。 程序文件set-group-id,则相应进程EGID被设置成这个程序文件的属组ID。 b. 程序文件没有set-user-id,则相应进程EUID不变。 程序文件没有set-group-id,则相应进程EGID不变。 注意,在exec(2)过程中,总是先设置好EUID/EGID,再将其复制到SUID/SGID。 如果当前EUID为0,任意调用setuid()同时设置RUID、EUID、SUID。 如果当前EUID为0,任意调用setgid()同时设置RGID、EGID、SGID。 如果当前EUID不为0,形参uid等于RUID或者SUID,调用setuid()后当前EUID被 设置成形参uid,RUID、SUID不受影响。 如果当前EUID不为0,形参gid等于RGID或者SGID,调用setgid()后当前EGID被 设置成形参gid,RGID、SGID不受影响。 返回值 0 成功 -1 失败,errno被设置 错误值 EINVAL 形参uid、gid等不在合法范围内 EPERM 当前EUID不为0,形参指定不符合前面描述 属性 ___________________________________________________________ | ATTRIBUTE TYPE | ATTRIBUTE VALUE | | ____________________________|_____________________________| | MT-Level | setuid() and setgid() and| | | Async-Signal-Safe | |_____________________________|_____________________________| 参看 intro(2)、exec(2)、getgroups(2)、getuid(2)、attributes(5) stat(5) -------------------------------------------------------------------------- A: 下面是Solaris 7的setreuid(2)手册页 -------------------------------------------------------------------------- 系统调用 setreuid(2) 名字 setreuid - 设置RUID、EUID 摘要 #include int setreuid ( uid_t ruid, uid_t euid ); 描述 setreuid()设置RUID、EUID,最终可能导致SUID改变。 如果形参ruid为-1,RUID不变。如果形参euid为-1,EUID不变。形参ruid、euid 可以不同。 如果EUID为0,形参ruid、euid可以是任意合法值。 如果EUID不为0,形参ruid可以等于当前RUID、当前EUID,形参euid可以等于当 前RUID、当前EUID、当前SUID。 两种情况下,如果RUID被成功修改(形参ruid不为-1),或者EUID被成功修改(形 参euid不为-1)并且不等于最终RUID,则最终SUID被设置成最终EUID。 返回值 0 成功 -1 失败,errno被设置 错误值 EINVAL 形参ruid、euid等不在合法范围内。 /usr/include/limits.h中定义了合法范围 [0, 2147483647(UID_MAX)]。 EPERM 当前EUID不为0,形参指定不符合前面描述。 用法 一个set-user-id进程调用setreuid()修改当前EUID成当前RUID后,依然可以调 用setreuid()修改当前EUID成当前SUID 参看 exec(2)、getuid(2)、setregid(2)、setuid(2) -------------------------------------------------------------------------- D: scz@nsfocus 从Solaris 2.6/7源码中可以看到,由于SUID的存在,存在很多安全隐患。 对于一个setuid-to-root的程序,如果需要永久放弃特权,应该在当前EUID为0的时 候调用setuid( not root )放弃特权。或者调用setreuid( not root, not root )放 弃特权。只有这两种正确办法,否则始终有机会重获特权。 -------------------------------------------------------------------------- /* * 最具有可移植性(Linux/FreeBSD/Solaris)的彻底释放特权的代码 */ setregid( getgid(), getgid() ); setreuid( getuid(), getuid() ); -------------------------------------------------------------------------- 假设当前ruid == 500、euid == 0、suid == 0, setreuid( -1, 500 ); <-- 临时放弃特权 ruid == 500、euid == 500、suid == 0 setreuid( -1, 0 ); <-- 恢复特权 ruid == 500、euid == 0、suid == 0 setuid( 500 ); <-- 永久放弃特权 ruid == euid == suid == 500 某些系统可能不支持SUID,假设当前ruid == 500、euid == 0, setuid( 0 ); <-- 现在ruid == euid == 0 setreuid( -1, 500 ); <-- 现在ruid == 0、euid == 500 seteuid( 0 ); <-- 现在ruid == euid == 0 显然在某些shellcode编写过程中,应该是这个调用顺序成功几率最大: seteuid( 0 ); setuid( 0 ); 下面这个程序演示如何调用setreuid()、setregid()永久放弃setuid、setgid特权 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o ugtest ugtest.c */ #include #include #include #include int main ( int argc, char * argv[] ) { uid_t temp_ruid, temp_euid, orig_ruid, orig_suid; gid_t temp_rgid, temp_egid, orig_rgid, orig_sgid; orig_ruid = temp_ruid = getuid(); orig_suid = temp_euid = geteuid(); orig_rgid = temp_rgid = getgid(); orig_sgid = temp_egid = getegid(); printf( "ruid = %d euid = %d suid = %d\n", ( int )temp_ruid, ( int )temp_euid, ( int )orig_suid ); printf( "rgid = %d egid = %d sgid = %d\n", ( int )temp_rgid, ( int )temp_egid, ( int )orig_sgid ); if ( setregid( orig_rgid, orig_rgid ) < 0 ) { perror( "Faint setregid" ); } if ( setreuid( orig_ruid, orig_ruid ) < 0 ) { perror( "Faint setreuid" ); } temp_ruid = getuid(); temp_euid = geteuid(); temp_rgid = getgid(); temp_egid = getegid(); printf( "ruid = %d euid = %d\n", ( int )temp_ruid, ( int )temp_euid ); printf( "rgid = %d egid = %d\n", ( int )temp_rgid, ( int )temp_egid ); if ( setegid( orig_sgid ) < 0 ) { perror( "I see setegid" ); } if ( seteuid( orig_suid ) < 0 ) { perror( "I see seteuid" ); } return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- [scz@ /export/home/scz/src]> ls -l ./ugtest -rwsr-sr-x 1 warning3 root 5416 6月 7 15:29 ./ugtest* [scz@ /export/home/scz/src]> ./ugtest ruid = 500 euid = 501 suid = 501 <-- setuid-to- rgid = 100 egid = 0 sgid = 0 <-- setgid-to-root ruid = 500 euid = 500 <-- 即使整个过程中没有EUID为0的机会, rgid = 100 egid = 100 还是成功地永久释放了特权 I see setegid: Not owner I see seteuid: Not owner [scz@ /export/home/scz/src]> 实际上Solaris下很多程序是利用setuid()、setgid()来释放特权的,以后检查源代 码,可以注意这个问题,很可能存在未真正永久释放特权的情况。 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系统中如何检查内存泄漏、腐烂 A: Sun Microsystems DOCUMENT ID: 2846-01 http://access1.sun.com/cgi-bin/rinfo2html?284601.faq A: Sun Microsystems 2001-04-25 FAQ ID - 3460 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/3460 Document ID - 3460 http://sunsolve.sun.com/search/document.do?assetkey=1-30-3460-1 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 FAQ ID - 3061 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/3061 库函数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: scz@nsfocus 下面以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: scz@nsfocus 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: yjl 2001-01-07 其实能支持中文,只是默认没有相应字体而已,但此时Unix Server上不是有相应字 体吗?拿过来就有了。 cd /usr/openwin/lib/locale/zh/X11/fonts/75dpi tar cvf /tmp/solaris.tar * FTP取回到PC,比如解压到WinaXe_Plus\FONTS\solaris目录下, 1) 重命名fonts.alias为fonts.ali 2) 将该目录下的.Z压缩文件都解开,一堆.pcf文件 3) 修改fonts.dir,将其中第一列的文件名改成不带.Z的 4) 运行XSettings,设置X Server的字体路径,将该目录添加进去 5) 运行XSession启动PC X Server,通过XDMCP登录Solaris 8 6) /opt/SUNWspro/WS5.0/bin/workshop启动无误 注意,这里有一个到PC的TCP连接,如有防火墙设置,请自行修改。此外,有必要提 醒的是,PC X Server缺省安装后几乎没有访问控制,历史上那些著名的X攻击...... A: knightmare@SMTH 还有一种方法就是xfs server。在目标主机上启动xfs server,在PC X Server中指 定目标主机提供的xfs server,这样就可以显示中文了。 9.8 如何让Solaris Console保持在字符登录界面,同时可以远程使用PC X Server Q: 我在VMware Workstation 3.0上装了一个x86/Solaris 8,想让console保持在字符登 录界面,同时启动PC X Server通过XDMCP远程登录CDE,不成功。 A: scz@nsfocus 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 Solaris怎样将第二块网卡名改成hme0 Q: 主板上的网卡坏了,新插一块,但名称怎么改过来 A: lisuit@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@nsfocus 上例在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@nsfocus 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: scz@nsfocus 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: scz@nsfocus 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 DOCUMENT ID: 3281-05 http://access1.sun.com/cgi-bin/rinfo2html?328105.faq 如果不想通过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@nsfocus 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@nsfocus 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 FAQ ID - 1579 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/1579 下面以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@nsfocus 有三个文件需要注意,/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 Infodoc ID - 15659 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/15659 Document ID - 15659 http://sunsolve.sun.com/pub-cgi/retrieve.pl?doc=infodoc/15659 http://sunsolve.sun.com/pub-cgi/Redir.pl?doc=infodoc/15659 http://sunsolve.sun.com/search/document.do?assetkey=1-9-15659-1 所谓虚拟网络接口指一个物理接口多个不同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 Infodoc ID - 16369 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/16369 下面是一个启动脚本举例 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@nsfocus 下面以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下如何检查网卡混杂模式 A: scz@nsfocus -------------------------------------------------------------------------- #! /sbin/sh # # # @(#)check_promisc.sh 0.01 aleph 2001/10/25 NSFocus Copyleft 2001-2010 # ~~~~~~~~~~~~~~~~ # Notice here is copyleft but not copyright, enjoy it by yourself, :-) # # ------------------------------------------------------------------------ # File : check_promisc.sh for checking SPARC/Solaris hme interface # Version : 0.01 aleph # Platform : SPARC/Solaris 2.6/7 # Author : NSFocus Security Team # : http://www.nsfocus.com # : scz < mailto: scz@nsfocus.com > # Date : 2001-10-25 10:29 # Modify : # # # Now only checking hme interface, if you find some error, please tell me. # I'm sorry for my poor English, :-( # RELEASE=`uname -r` PROMISCFLAG=0 # echo ${RELEASE} if [ "${RELEASE}" = "5.6" ] ; then # echo "5.6" PROMISCFLAG=`echo "*hmeup+0t636 /X" | adb -k /dev/ksyms /dev/mem | \ grep ":" | awk '{print $2;}'` if [ ${PROMISCFLAG} -eq 3 ] ; then echo "Warning: Interface hme0 is in PROMISCUOUS mode." elif [ ${PROMISCFLAG} -ne 1 ] ; then PROMISCFLAG=`echo "*hmeup+0t640 /X" | adb -k /dev/ksyms /dev/mem | \ grep ":" | awk '{print $2;}'` if [ ${PROMISCFLAG} -eq 3 ] ; then echo "Warning: Interface hme0 is in PROMISCUOUS mode." fi fi exit 0 elif [ "${RELEASE}" = "5.7" ] ; then # echo "5.7" PROMISCFLAG=`echo "*hmeup+0t900 /X" | adb -k /dev/ksyms /dev/mem | \ grep ":" | awk '{print $2;}'` if [ ${PROMISCFLAG} -eq 3 ] ; then echo "Warning: Interface hme0 is in PROMISCUOUS mode." fi exit 0 else echo "This script only can check promisc in SPARC/Solaris 2.6/7" exit 1 fi -------------------------------------------------------------------------- 上述脚本可以利用awk命令修改得更加通用些,回头来补。对于Solaris 8, netstat -k | grep -i promsic可以看到网卡混杂模式标志。配 合modinfo命令观察bufmod、pfmod模块是否加载,进一步确认。snoop如果指定了过 滤规则,pfmod才会加载,否则只加载bufmod。下面是x86/Solaris 8的一个演示, [root@ /]> netstat -k pcn0 | grep -i promisc rbytes64 20119263 ifspeed 0 media unknown promisc off [root@ /]> ^^^^^^^^^^^ 注意这里 D: scz@nsfocus 2001-10-12 10:30 总结一下,就这么两条命令,显示1表示正常,显示3表示网卡在混杂模式 SPARC/Solaris 2.6 32-bit kernel mode echo "*hmeup+0t636 /X" | /usr/bin/adb -k /dev/ksyms /dev/mem SPARC/Solaris 7 64-bit kernel mode echo "*hmeup+0t900 /X" | /usr/bin/adb -k /dev/ksyms /dev/mem 无论kernel mode是32-bit还是64-bit,这个标志都是4字节大小 上述检查方法不稳定,和内核补丁有关,具体来说就是偏移发生变化。检查 /usr/include/sys/hme.h文件更新日期以及文件中struct hme的变化,确认和内核补 丁相关。 内核状态标志为1时正常,为3时进入混杂模式。 10.9 FreeBSD下ifconfig的man手册 A: scz@nsfocus 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@nsfocus 下面举例说明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@nsfocus 2001-09-25 00:35 fxp0、xl0(3Com)这两种驱动可以动态修改MAC地址,rl0这种驱动不能,看样子是不 支持这些修改? rl0修改MAC地址后,外部可以看到这个MAC地址,但无法通信,比如ping不通,可能 是网卡驱动不支持的缘故,比如网卡的Working Address Register已经被修改,而驱 动中相应数据结构未被修改,就会出现这种现象。 D: scz@nsfocus 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未测试,应该也是可以 的。 XP下MAC地址在注册表中的相应位置: 192.168.7.2 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{5123CA0D-2833-428F-B9EB-638CC0DEB1B2} HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\0001 NetCfgInstanceId REG_SZ {5123CA0D-2833-428F-B9EB-638CC0DEB1B2} NetworkAddress REG_SZ 000000111111 192.168.7.152 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{3F2F7F53-94CC-4C83-B683-4308C573712D} HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\0003 NetCfgInstanceId REG_SZ {3F2F7F53-94CC-4C83-B683-4308C573712D} NetworkAddress REG_SZ 000000000000 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: scz@nsfocus 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@nsfocus 下面举例说明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: scz@nsfocus 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: scz@nsfocus 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@nsfocus 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: scz@nsfocus 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: scz@nsfocus 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: scz 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跳。 -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跳。 -n 不做反向域名解析 -p 设置探测分组起始UDP端口,缺省是33434。traceroute期望在 [base,base+nhops-1]上无端口被侦听。 -v 冗余输出 -w 设置等待ICMP报文时间(单位是秒),缺省是5秒。 10.15 SPARC/Solaris 8 snoop(1M)手册页 A: scz@nsfocus 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: scz@nsfocus 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 10.19 Solaris如何手工配置TCP/IP协议栈 A: 修改/etc/hosts如下: 10.10.5.26 test 修改/etc/netmasks如下: 10.10.5.0 255.255.255.0 修改/etc/defaultrouter如下: 10.10.5.254 reboot后即可生效。如果想热生效,可以这样做: /sbin/ifconfig pcn0 10.10.5.26 netmask 255.255.255.0 broadcast 10.10.5.255 up /usr/sbin/route -n add default 10.10.5.254 还可以这样添加缺省路由: /usr/sbin/route -n add 0 10.10.5.254 /usr/sbin/route -n add 0.0.0.0/0 10.10.5.254 /usr/sbin/route -n add 0.0.0.0 -netmask 0.0.0.0 10.10.5.254 用如下命令清空路由表: route -nf 10.20 Debian如何更改网卡名 Q: 在VMware中装了Debian 4.0,当时网卡名是eth0,后来将虚拟机文件移动到另一个目 录中,网卡名变成了eth1。此时若将虚拟机文件移动回原目录,网卡名恢复成eth0。 有什么办法在虚拟机文件离开原目录的情况下继续保持网卡名为eth0吗。 A: # cd /etc # grep -R eth1 * udev/rules.d/z25_persistent-net.rules:SUBSYSTEM=="net", DRIVERS=="?*", ATTRS{address}=="00:0c:29:23:03:f3", NAME="eth1" # more udev/rules.d/z25_persistent-net.rules # This file was automatically generated by the /lib/udev/write_net_rules # program, probably run by the persistent-net-generator.rules rules file. # # You can modify it, as long as you keep each rule on a single line. # MAC addresses must be written in lowercase. # PCI device 0x1022:0x2000 (pcnet32) SUBSYSTEM=="net", DRIVERS=="?*", ATTRS{address}=="00:0c:29:01:45:95", NAME="eth0" # PCI device 0x1022:0x2000 (pcnet32) SUBSYSTEM=="net", DRIVERS=="?*", ATTRS{address}=="00:0c:29:23:03:f3", NAME="eth1" 编辑/etc/udev/rules.d/z25_persistent-net.rules文件,删除eth0相应的两行,再 将eth1更名为eth0,重启使之生效。 另一个方案需要在console上操作: # ifconfig eth1 down # ip link set eth1 name eth0 # ifconfig eth0 up 这个方案是热生效的,但重启后就没了。建议使用第一种方案。 10.21 Debian上如何阻止本机发送端口不可达ICMP报文、RST报文 Q: 我在用raw socket或PF_PACKET编程,考虑两种情况: 1) 本机在2、3层直接发送DNS查询报文,收到DNS响应后本机会发送端口不可达ICMP报文。 2) 本机在2、3层直接发送SYN报文,收到SYN+ACK之后本机会发送RST报文。 这两种情况都是我不希望看到的。 A: modprobe ip_tables iptables -A OUTPUT -p icmp -m icmp --icmp-type port-unreachable -j DROP iptables -A OUTPUT -p tcp -m tcp --tcp-flags RST RST -j DROP 参看man手册: http://linux.die.net/man/8/iptables iptables -m icmp -h iptables -p tcp -h iptables -p ip -h 前面的-m icmp、-m tcp不是必须的。 Q: 如何禁止192.168.7.2之外的其它IP访问本机(192.168.7.20),同时允许本机访问任 意其它IP? A: backend@nsfocus 2012-05-30 16:47 iptables -A INPUT -p all -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A INPUT -p ip -s 127.0.0.1,192.168.7.20,192.168.7.2 -j ACCEPT iptables -P INPUT DROP 第一条非常重要,这样PFW才是基于状态的。第二条建议允许源IP为127.0.0.1,否则 登录KDE时会很慢,这涉及从127.0.0.1到127.0.0.1:111/TCP的访问;如果不允许源 IP为127.0.0.1,也能登录KDE;你可能还想允许源IP为192.168.7.20。第三条使得 INPUT的默认策略是DROP。 查看规则: iptables -L iptables -nL 导出规则: iptables-save Q: 如何停用iptables? D: 没法卸载ip_tables模块: # lsmod | grep table Module Size Used by iptable_filter 936 1 ip_tables 7798 1 iptable_filter x_tables 9057 3 xt_state,iptable_filter,ip_tables # rmmod ip_tables ERROR: Module ip_tables is in use by iptable_filter 我没找到简单办法停用iptables,只好这样变通: iptables -P INPUT ACCEPT iptables -F 10.22 Debian上如何禁用IPv6 A: 以Debian lenny为例。 在/etc/modprobe.d/blacklist中尾部增加: blacklist ipv6 编辑/etc/hosts,将IPv6相关条目注释(或删除)掉。 10.23 如何禁用SSLv2 A: liuzhixu SSLv2在协商密钥过程中所带的加密算法列表由于没有签名,可以被中间人替换为较 弱的加密算法,从而被解密出明文。最好禁用SSLv2。 SSLProtocol是mod_ssl的环境变量,参看: http://httpd.apache.org/docs/2.0/mod/mod_ssl.html Apache的配置如下: # enable SSLv3 and TLSv1, but not SSLv2 SSLProtocol all -SSLv2 分别使用SSLv2、SSLv3尝试连接,前者应该连接失败,服务端看到客户端的SSL版本 是2,就直接关闭连接了。 openssl s_client -ssl2 -connect host:port openssl s_client -ssl3 -connect host:port SSL的版本变化是: SSLv1、SSLv2、SSLv3、TLSv1、TLSv2、TLSv3 参看: http://en.wikipedia.org/wiki/Transport_Layer_Security A: scz IIS里禁用SSLv2: -------------------------------------------------------------------------- Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server] "Enabled"=dword:00000000 -------------------------------------------------------------------------- 10.24 如何架设SSH Tunnel Q: 我在内网,外网有一台SSH Server。现在我想架设SSH Tunnel,如何做? A: 假设已知SSH Server的一组有效user/pass,在内网主机上执行如下命令: Windows: plink.exe -2 -4 -C -N -T -D 127.0.0.1:4321 @:22 plink.exe -2 -4 -C -N -T -D 127.0.0.1:4321 -l -pw "" :22 (建个快捷方式) Linux: ssh -f -2 -4 -CNTg -D 127.0.0.1:4321 @ 此时内网主机会侦听"127.0.0.1:4321",将这个当成一个SOCKS5代理即可。如果外网 SSH Server可以做DNS Client,建议同时启用远程DNS解析。 Q: 内网、外网各有一台SSH Server。现在我想架设反向SSH Tunnel,使得可以从外网访 问内网,如何做? A: 在内网SSH Server上执行如下命令: ssh -f -2 -4 -CNT -R 4322:127.0.0.1:22 @ 在外网SSH Server上执行如下命令: ssh -f -2 -4 -CNTg -D 127.0.0.1:4321 -p 4322 @127.0.0.1 此时外网主机会侦听"127.0.0.1:4321",将这个当成一个从外网访问内网的SOCKS5代 理即可。 D: 架设反向SSH Tunnel时,不一定从内网SSH Server开始,可以从内网Windows主机开 始: plink.exe -2 -4 -C -N -T -R 4322::22 @:22 plink.exe -2 -4 -C -N -T -R 4322::22 -l -pw "" :22 (建个快捷方式) 只要从这台内网Windows主机可以正常访问内网SSH Server即可。 D: 反向SSH Tunnel给内网安全带来挑战。假设没有封禁访问外网22/TCP口,此时内网的 SSH Server很可能被用成反向SOCKS5代理,从而大大方便从外网访问内网其它主机。 其实只要允许从内网访问外网,即使22/TCP被封了也一样存在风险,这里就不讨论这 些极端情形了。 Q: 如何进行端口转发? A: Windows plink.exe -2 -4 -C -N -T -L ::: -l -pw "" :22 (建个快捷方式) Linux ssh -f -2 -4 -CNTg -L ::: @ Q: 如何进行反向端口转发? A: Windows plink.exe -2 -4 -C -N -T -R ::: -l -pw "" :22 (建个快捷方式) Linux ssh -f -2 -4 -CNT -R ::: @ 10.25 利用privoxy将SOCKS5代理转换为HTTP代理 A: zd 2011-12-15 09:23 1) Linux平台 aptitude install privoxy ssh -CfNg -D 127.0.0.1:4321 root@x.x.x.x 编辑/etc/privoxy/config: #listen-address localhost:8118 listen-address 10.17.2.18:8080 forward-socks5 / 127.0.0.1:4321 . /etc/init.d/privoxy restart 此时10.17.2.18:8080成为HTTP代理。 netstat -natp 2) Windows平台 PLINK.EXE -C -N -D 127.0.0.1:4321 -l root -pw admin x.x.x.x cd "C:\Program Files\privoxy" 编辑config.txt: #listen-address 127.0.0.1:8118 listen-address 127.0.0.1:8080 forward-socks5 / 127.0.0.1:4321 . privoxy.exe 此时127.0.0.1:8080成为HTTP代理。 11. package相关问题 11.0 在SPARC/Solaris 8上手工安装libpcap A: scz@nsfocus 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: scz@nsfocus & deepin 这是一个"Frequently Unanswered Question",遗憾的是,今天它正式成为 <>中的一员,:-) 假设存在/export/home/top-3.5beta9-sol7-sparc-local二进制安装包,先进行伪安 装 pkgadd -s /var/spool/pkg -d /export/home/top-3.5beta9-sol7-sparc-local 直接回车,之后会出现一个/var/spool/pkg/WLtop目录,编辑 /var/spool/pkg/WLtop/pkginfo文件,比如修改对应行 PKG=WLtop BASEDIR=/usr/local 成为 PKG=GNUtop <-- 以后可以用pkginfo -l GNUtop查看,用pkgrm GNUtop删除 BASEDIR=/export/home/scz <-- 期望的目标路径 如果自己对打包非常熟悉,修改其余行也无妨,否则不要修改其余行 mv /var/spool/pkg/WLtop /var/spool/pkg/GNUtop 如果没有修改"PKG=WLtop"这一行,则不必做上述操作。 pkgadd -d /var/spool/pkg GNUtop 此时肯定会出错,输出类似的错误信息 file size <166> expected <173> actual file cksum <13941> expected <14625> actual 现在回头修改/var/spool/pkg/GNUtop/pkgmap文件,找到包含166和13941的行 1 i pkginfo 166 13941 909480065 修改成 1 i pkginfo 173 14625 909480065 再次执行 pkgadd -d /var/spool/pkg GNUtop 熟悉的安装提示出现,只不过这次安装到了/export/home/scz/目录下,而不是缺省 的/usr/local目录。 可以用 pkginfo -l GNUtop 和 pkgparam GNUtop 查看已安装包信息,用 pkgrm GNUtop 删除已安装包。 此时还可以执行 pkgtrans -s /var/spool/pkg /tmp/scz_deepin GNUtop 会在/tmp目录下得到一个二进制安装包scz_deepin,于是你可以执行常规安装命令 pkgadd -d /tmp/scz_deepin 这个包此时名为GNUtop,安装的目标路径是/export/home/scz。 最后可以删除伪安装产生的中间文件 rm -rf /var/spool/pkg/GNUtop A: scz@nsfocus 下面这个脚本pkginstall.sh将修正一个软件包的目标安装路径 -------------------------------------------------------------------------- #! /bin/sh # # Solaris下如何将二进制软件包安装到指定目标路径下 # # Q: pkgadd无法指定目标路径,而我不想使用缺省安装路径/usr/local # # A: scz@nsfocus & deepin # if [ $# -ne 2 ] then echo "Usage: $0 " exit 1 fi SPOOLDIR="/tmp/scz_pkg" if [ ! -f $1 ] then echo "Error: $1 doesn't exist." exit 1 fi /usr/bin/rm -rf $SPOOLDIR /usr/bin/mkdir $SPOOLDIR # 先进行伪安装 echo "" | pkgadd -s $SPOOLDIR -d "$1" 2> /dev/null PACKAGEDIR="`/usr/bin/ls $SPOOLDIR`" # echo $PACKAGEDIR # 如果target_basedir并未改变,则退出 /usr/bin/grep "^BASEDIR=$2$" $SPOOLDIR/$PACKAGEDIR/pkginfo > /dev/null # echo $? if [ $? -eq 0 ] then echo "Error: target_basedir <$2> not to chanage." /usr/bin/rm -rf $SPOOLDIR exit 1 fi # 避免竞争环境错误 /usr/bin/rm -rf $SPOOLDIR/$PACKAGEDIR/pkginfo.scz /usr/bin/sed -e "/^BASEDIR=/c\\ BASEDIR=$2" $SPOOLDIR/$PACKAGEDIR/pkginfo > $SPOOLDIR/$PACKAGEDIR/pkginfo.scz # /usr/bin/cat $SPOOLDIR/$PACKAGEDIR/pkginfo.scz /usr/bin/mv $SPOOLDIR/$PACKAGEDIR/pkginfo.scz $SPOOLDIR/$PACKAGEDIR/pkginfo # echo $PACKAGEDIR # 避免竞争环境错误 /usr/bin/rm -rf $SPOOLDIR/pkgadd.scz pkgadd -d $SPOOLDIR $PACKAGEDIR 2> $SPOOLDIR/pkgadd.scz # /usr/bin/cat $SPOOLDIR/pkgadd.scz LINE=`/usr/bin/grep -in "pkgadd: ERROR:" $SPOOLDIR/pkgadd.scz | /usr/bin/cut -f 1 -d":"` LINE=`expr $LINE + 1` # echo $LINE # 此时肯定会出错 ERRSIZE=`/usr/bin/sed -e "${LINE}p" -n $SPOOLDIR/pkgadd.scz | /usr/bin/cut -f 2 -d'<' | /usr/bin/cut -f 1 -d'>'` # echo $ERRSIZE SIZE=`/usr/bin/sed -e "${LINE}p" -n $SPOOLDIR/pkgadd.scz | /usr/bin/cut -f 3 -d'<' | /usr/bin/cut -f 1 -d'>'` # echo $SIZE LINE=`expr $LINE + 1` ERRCKSUM=`/usr/bin/sed -e "${LINE}p" -n $SPOOLDIR/pkgadd.scz | /usr/bin/cut -f 2 -d'<' | /usr/bin/cut -f 1 -d'>'` # echo $ERRCKSUM CKSUM=`/usr/bin/sed -e "${LINE}p" -n $SPOOLDIR/pkgadd.scz | /usr/bin/cut -f 3 -d'<' | /usr/bin/cut -f 1 -d'>'` # echo $CKSUM /usr/bin/rm -rf $SPOOLDIR/pkgadd.scz LINE=`/usr/bin/grep -in "1 i pkginfo $ERRSIZE $ERRCKSUM" $SPOOLDIR/$PACKAGEDIR/pkgmap | /usr/bin/cut -f 1 -d":"` # 避免竞争环境错误 /usr/bin/rm -rf $SPOOLDIR/$PACKAGEDIR/pkgmap.scz /usr/bin/sed -e "${LINE}s/ $ERRSIZE $ERRCKSUM / $SIZE $CKSUM /" $SPOOLDIR/$PACKAGEDIR/pkgmap > $SPOOLDIR/$PACKAGEDIR/pkgmap.scz /usr/bin/mv $SPOOLDIR/$PACKAGEDIR/pkgmap.scz $SPOOLDIR/$PACKAGEDIR/pkgmap # pkgadd -d $SPOOLDIR $PACKAGEDIR pkgtrans -s $SPOOLDIR $1 $PACKAGEDIR 2> /dev/null # 删除伪安装产生的中间文件 /usr/bin/rm -rf $SPOOLDIR # pkgadd -d $1 echo "Ok: check your $1" exit 0 -------------------------------------------------------------------------- man -s 1M pkgadd、man -s 4 admin可以看到Sun公司的正规解决办法,最简单的做 法是创建这样一个文件scz.install -------------------------------------------------------------------------- mail= instance=unique partial=ask runlevel=ask idepend=ask rdepend=ask space=ask setuid=ask conflict=ask action=ask basedir=ask <-- 注意这里 -------------------------------------------------------------------------- 执行pkgadd -a scz.install -d scz.pkg,之后安装过程中会提示你输入目标安装路 径,或者干脆在scz.install文件中直接指定basedir=<...>。如果不指定-a参数,系 统缺省使用/var/sadm/install/admin/default文件,此时basedir=default,所以造 成无法指定目标安装路径的假象,可以考虑修改default文件。参看pkgask(1M)手册 页了解如何生成自动应答文件,以便编写后台批处理安装脚本,我也不会这招。 A: lisuit@SMTH 2002-01-19 14:49 pkgadd -R ... A: scz@nsfocus 上次pkginstall.sh利用pkgadd的错误提示信息获取修改pkginfo后有效的size、 cksum(位于pkgmap文件中),今天(2001-04-05)发现不需要这样做,有更正确的办法。 -------------------------------------------------------------------------- #! /bin/sh if [ $# -ne 2 ] then echo "Usage: $0 " exit 1 fi SPOOLDIR="/tmp/scz_pkg" if [ ! -f $1 ] then echo "Error: $1 doesn't exist." exit 1 fi /usr/bin/rm -rf $SPOOLDIR /usr/bin/mkdir $SPOOLDIR # 先进行伪安装 echo "" | pkgadd -s $SPOOLDIR -d "$1" 2> /dev/null PACKAGEDIR="`/usr/bin/ls $SPOOLDIR`" # echo $PACKAGEDIR # 如果target_basedir并未改变,则退出 /usr/bin/grep "^BASEDIR=$2$" $SPOOLDIR/$PACKAGEDIR/pkginfo > /dev/null # echo $? if [ $? -eq 0 ] then echo "Error: target_basedir <$2> not to chanage." /usr/bin/rm -rf $SPOOLDIR exit 1 fi # 避免竞争环境错误 /usr/bin/rm -rf $SPOOLDIR/$PACKAGEDIR/pkginfo.scz /usr/bin/sed -e "/^BASEDIR=/c\\ BASEDIR=$2" $SPOOLDIR/$PACKAGEDIR/pkginfo > $SPOOLDIR/$PACKAGEDIR/pkginfo.scz # /usr/bin/cat $SPOOLDIR/$PACKAGEDIR/pkginfo.scz /usr/bin/mv $SPOOLDIR/$PACKAGEDIR/pkginfo.scz $SPOOLDIR/$PACKAGEDIR/pkginfo SIZE=`/bin/wc -c $SPOOLDIR/$PACKAGEDIR/pkginfo | /usr/bin/cut -f6 -d' '` # echo $SIZE CKSUM=`/bin/sum $SPOOLDIR/$PACKAGEDIR/pkginfo | /usr/bin/cut -f1 -d' '` # echo $CKSUM MTIME=`/usr/bin/grep 'pkginfo' $SPOOLDIR/$PACKAGEDIR/pkgmap | /usr/bin/cut -f6 -d' '` # echo $MTIME # 避免竞争环境错误 /usr/bin/rm -rf $SPOOLDIR/$PACKAGEDIR/pkgmap.scz /usr/bin/sed -e "/^1 i pkginfo/c\\ 1 i pkginfo $SIZE $CKSUM $MTIME" $SPOOLDIR/$PACKAGEDIR/pkgmap > $SPOOLDIR/$PACKAGEDIR/pkgmap.scz /usr/bin/mv $SPOOLDIR/$PACKAGEDIR/pkgmap.scz $SPOOLDIR/$PACKAGEDIR/pkgmap # pkgadd -d $SPOOLDIR $PACKAGEDIR pkgtrans -s $SPOOLDIR $1 $PACKAGEDIR 2> /dev/null # 删除伪安装产生的中间文件 /usr/bin/rm -rf $SPOOLDIR # pkgadd -d $1 echo "Ok: check your $1" exit 0 -------------------------------------------------------------------------- 11.2 Solaris下如何自己定制二进制安装包 A: deepin & scz@nsfocus [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: scz@nsfocus 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@nsfocus 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@nsfocus # 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 A: scz 编辑/etc/apt/sources.list: -------------------------------------------------------------------------- #deb http://ftp.de.debian.org/debian stable main non-free contrib #deb http://ftp.debian.org/debian stable main non-free contrib deb http://ftp.cn.debian.org/debian stable main non-free contrib deb-src http://ftp.cn.debian.org/debian/ stable main non-free contrib -------------------------------------------------------------------------- apt-get update -u aptitude install rsh-client aptitude remove rsh-client mkdir -p /root/src/rsh cd /root/src/rsh/ apt-get source rsh-client 用最后一条命令下载rsh-client的源代码,存放在当前目录下。 11.6 Solaris下如何知道某包中有哪些文件 A: scz@nsfocus 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等工具进一步反汇编检查。 11.8 如何用tar直接解.tar.bz2文件 Q: 解.tar.gz用"tar xvfz ...",如何用tar直接解.tar.bz2文件? A: tar xvfj xxx.tar.bz2 11.9 Debian上如何知道指定文件属于哪个安装包 A: # dpkg -S `which objdump` binutils: /usr/bin/objdump # dpkg -S `which dos2unix` dos2unix: /usr/bin/dos2unix # apt-get install dos2unix 11.10 装完Debian发现少了fprintf的man手册 A: qfp & tk apt-get install manpages-dev 11.11 查看fprintf的man手册时遭遇"can't find macro file gb.tmac" A: tk@nsfocus apt-get install groff 11.12 Solaris上如何安装package Q: N年不用Solaris,不会安装package了 A: gzip -d binutils-2.17-sol8-sparc-local.gz pkgadd -d binutils-2.17-sol8-sparc-local 11.13 Solaris上如何卸载package Q: 我用pkgadd安装了一个低版本的openssl,现在想卸载它,但我不知道包名。 A: # pkgchk -l -p /usr/local/ssl/bin/openssl Pathname: /usr/local/ssl/bin/openssl Type: regular file Expected mode: 0755 Expected owner: bin Expected group: bin Expected file size (bytes): 1340084 Expected sum(1) of contents: 43550 Expected last modification: Apr 05 05:50:23 PM 2003 Referenced by the following packages: SMCossl Current status: installed # pkgrm SMCossl 11.14 如何单独获得Solaris编译环境 Q: 我需要安装哪些包 A: 需要下列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之 前。 D: scz@nsfocus 2001-11-26 09:40 把Solaris 7安装光盘放入cdrom,如果/usr/sbin/vold启动了,就不需要其他操作( volcheck和eject),直接cd /cdrom即可。最直接的办法是 # cd /cdrom/zh_sol_7_sparc_sun_srvr/Solaris_2.7 # pkgadd -d ./Product SUNWsprot Solaris 7缺省spooldir是/var/spool/pkg/,具体man -s 1M pkgadd。如果存在 /var/spool/pkg/SUNWsprot/目录,则直接pkgadd就可以选择安装: # cd /cdrom/zh_sol_7_sparc_sun_srvr/Solaris_2.7 # pkgadd -s /var/spool/pkg -d ./Product SUNWsprot Transferring package instance # pkgadd SUNWsprot 先进行一次伪安装,然后直接指定包名字进行安装。伪安装就是个简单的复制过程: # cp -R /cdrom/zh_sol_7_sparc_sun_srvr/Solaris_2.7/Product/SUNWsprot /var/spool/pkg # pkginfo -l SUNWsprot <-- 这样就不用pkginfo | grep了 11.15 在Debian 4.0上安装acroread A: 访问如下网站了解更多信息: http://www.debian-multimedia.org/ 1) wget http://www.debian-multimedia.org/pool/main/d/debian-multimedia-keyring/debian-multimedia-keyring_2008.10.16_all.deb dpkg -i debian-multimedia-keyring_2008.10.16_all.deb 2) 编辑/etc/apt/sources.list,增加如下内容: deb http://www.debian-multimedia.org stable main deb-src http://www.debian-multimedia.org sid main 3) apt-get update -u apt-get install acroread 11.16 使用apt系列工具时总是出现"Segment fault" D: 原因不明,有一些相关的解决方案,但都不确定,仅供参考。 vi /etc/apt/apt.conf -------------------------------------------------------------------------- APT::Cache-Limit "20000000"; -------------------------------------------------------------------------- vi /etc/apt/sources.list -------------------------------------------------------------------------- deb http://ftp.de.debian.org/debian stable main non-free contrib -------------------------------------------------------------------------- # rm /var/cache/apt/*.bin # rm -rf /var/lib/apt/lists # mkdir -p /var/lib/apt/lists/partial # apt-get update -u 11.17 dpkg: 'update-rc.d' not found on PATH Q: 使用apt系列工具时遭遇"dpkg: 'update-rc.d' not found on PATH" A: ln -s /bin/false /tmp/update-rc.d export PATH=$PATH:/tmp apt-get install file-rc 如果还碰上一些问题,根据提示信息尝试"-f install"。成功后应该有: /usr/sbin/update-rc.d 记得恢复PATH环境变量: rm /tmp/update-rc.d export PATH=... 11.18 "apt-get update -u"时遭遇"There is no public key available for the following key IDs" Q: # apt-get update -u ... ... Reading package lists... Done W: There is no public key available for the following key IDs: 4D270D06F42584E6 W: You may want to run apt-get update to correct these problems A: apt-get install debian-keyring debian-archive-keyring apt-key update apt-get update -u Q: # cat /etc/apt/sources.list deb http://debian.cn99.com/debian etch main non-free contrib deb http://debian.cn99.com/debian stable main non-free contrib # apt-get update -u ... ... W: 以下 key ID 没有可用的公钥: 9AA38DCD55BE302B W: GPG error: http://debian.cn99.com etch Release: 由于没有公钥,下列签名无法进行验证: NO_PUBKEY 9AA38DCD55BE302B W: GPG error: http://security.debian.org etch/updates Release: 由于没有公钥,下列签名无法进行验证: NO_PUBKEY 9AA38DCD55BE302B W: 您可能需要运行 apt-get update 来解决这些问题 A: gpg --keyserver wwwkeys.eu.pgp.net --recv-key 9AA38DCD55BE302B gpg --export 9AA38DCD55BE302B | apt-key add - Q: # apt-get update -u ... W: GPG error: http://ftp.de.debian.org stable Release: 由于没有公钥,下列签名无法进行验证: NO_PUBKEY AED4B06F473041FA W: GPG error: http://ftp.debian.org stable Release: 由于没有公钥,下列签名无法进行验证: NO_PUBKEY AED4B06F473041FA ... A: gpg --keyserver pgp.mit.edu --recv-keys AED4B06F473041FA gpg --armor --export AED4B06F473041FA | apt-key add - Q: NO_PUBKEY 8B48AD6246925553 NO_PUBKEY 6FB2A1C265FFB764 A: apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 8B48AD6246925553 apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 6FB2A1C265FFB764 11.19 如何安装Perl模块 A: scz 先说一下Windows的情况,假设已经安装ActivePerl 5.14.1或更高版本。 > perl -v This is perl 5, version 14, subversion 1 (v5.14.1) built for MSWin32-x86-multi-thread > ppm 这个版本的ppm是GUI界面的,不必再记那么多命令了,全部是鼠标操作。缺省的 Repository只有一个: ActiveState Package Repository http://ppm4.activestate.com/MSWin32-x86/5.14/1401/package.xml 可以去"Edit->Preferences->Repositories"里增加Suggested: log4perl http://log4perl.sourceforge.net/ppm sisyphusion http://www.sisyphusion.tk/ppm/package.xml wxperl http://www.wxperl.co.uk/repository/package.xml 之后就能找到绝大多数模块,右键选择安装即可。假设需要安装IO::Socket::INET6, 在ppm中应该找IO-Socket-INET6,就是把::换成-。 D: scz 下面是5.8.0.804版的操作过程: > perl -v This is perl, v5.8.0 built for MSWin32-x86-multi-thread > ppm PPM - Programmer's Package Manager version 3.0.1. ppm> help Type 'help command' for more detailed help on a command. Commands: describe - describes packages in detail exit - exits the program help - prints this screen, or help on 'command' install - installs packages profiles - manage PPM profiles properties - describes installed packages in detail q - exits the program query - queries installed packages quickstart - a crash course in using PPM quit - exits the program remove - uninstalls packages repository - adds, removes, or sets repositories s - searches for packages in a repository - no help available search - searches for packages in a repository settings - view or set PPM options targets - views or sets target installer backends tree - shows package dependency tree uninstall - uninstalls packages upgrade - shows availables upgrades for installed packages version - displays the PPM version (3.0.1) Extra Help Topics: (not commands) ppm_migration - guide for those familiar with PPM prompt - how to interpret the PPM prompt unicode - notes about unicode author names ppm> search Getopt-Long Searching in Active Repositories 1. Getopt-Long [2.37] Module to handle parsing command line options 2. Getopt-Long [2.37] Getopt-Long 3. Getopt-Long-Descriptive [0.070] Getopt::Long with usage text 4. Getopt-Long-Descriptive [0.070] Getopt-Long-Descriptive 5. Getopt-Long-GUI [0.1] 6. Getopt-Long-GUI [0.1] Getopt-Long-GUI ppm> install Getopt-Long Successfully installed Getopt-Long version 2.37 in ActivePerl 5.8.0.804. ppm> query Getopt-Long Querying target 1 (ActivePerl 5.8.0.804) 1. Getopt-Long [2.37] Module to handle parsing command line options ppm> exit A: scz 在Debian上安装IO::Socket::INET6 $ aptitude install make $ perl -MCPAN -e shell cpan> h cpan> o conf cpan> o conf init 这一步不要选自动,一路手工配下去,重点是选一个可访问的CPAN下载(镜像)站,比 如: http://mirrors.163.com/cpan/ cpan> install YAML cpan> install IO::Socket::SSL cpan> install IO::Socket::INET6 cpan> q 如果前期配置正常的话,可以直接执行如下命令进行安装: $ perl -MCPAN -e "install IO::Socket::INET6" D: scz 在Linux下perl -MCPAN -e "install ..."时,有些网络模块带了make test,但我朝 寡妇王多牛B啊,铁定失败,接下来模块安装失败,但从上下文里看make已经成功了。 可以直接去~/.cpan/build/下找编译目录,自己执行make install,绕过make test 即可。当然,你得确认这个绕过是有道理的,这是有上下文的。 11.20 "man pthread_cond_wait"说没有手册页 A: apt-get install manpages-posix-dev aptitude install manpages-posix-dev 11.21 Dynamic MMap ran out of room. Please increase the size of APT::Cache-Start Q: 原来/etc/apt/sources.list只有stable,后来增加了testing: -------------------------------------------------------------------------- deb http://ftp.cn.debian.org/debian stable main non-free contrib deb-src http://ftp.cn.debian.org/debian/ stable main non-free contrib deb http://ftp.cn.debian.org/debian testing main non-free contrib deb-src http://ftp.cn.debian.org/debian/ testing main non-free contrib -------------------------------------------------------------------------- 首先我不知道这样做是否正确,其次执行"apt-get update -u"时碰上: E: Dynamic MMap ran out of room. Please increase the size of APT::Cache-Start. Current value: 25165824. (man 5 apt.conf) E: Unable to increase the size of the MMap as the limit of 20000000 bytes is already reached. D: 编辑/etc/apt/apt.conf,原来是20000000,现在改成40000000: -------------------------------------------------------------------------- APT::Cache-Limit "40000000"; -------------------------------------------------------------------------- 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 A: last -f wtmp 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 FAQ ID - 1570 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/1570 编辑/etc/default/cron,设置 CRONLOG 变量为 NO ,将关闭cron的日志 CRONLOG=NO 缺省是 CRONLOG=YES 12.4 /var/adm/lastlog文件看上去太大了 A: Sun Microsystems Infodoc ID - 22371 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/22371 # 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字节为单位)。 12.5 如何对付.bash_history A: export HISTFILE=/dev/null (或者 unset HISTFILE) cat /dev/null > ~/.bash_history history -c 如果可能的话,还可以: ln -sf /dev/null ~/.bash_history 或者 chmod 0 ~/.bash_history (还原,chmod 600 ~/.bash_history) 13. 进程相关问题 13.0 显示进程树 A: -------------------------------------------------------------------------- Usage: pstree [ -a ] [ -c ] [ -h | -H PID ] [ -l ] [ -n ] [ -p ] [ -u ] [ -A ] [ PID | USER ] pstree -V Display a tree of processes. -a show command line arguments -A use ASCII line drawing characters -c don't compact identical subtrees -h highlight current process and its ancestors -H PID highlight this process and its ancestors -l don't truncate long lines -n sort output by PID -p show PIDs; implies -c -u show uid transitions -V display version information PID start at this PID; default is 1 (init) USER show only trees rooted at processes of this user -------------------------------------------------------------------------- 推荐-npu和-al的组合。 # pstree init-+-NetworkManager |-NetworkManagerD |-acpid |-avahi-daemon---avahi-daemon |-cron |-dbus-daemon |-dhcdbd |-dirmngr |-events/0 |-6*[getty] |-hald---hald-runner-+-hald-addon-acpi | |-hald-addon-inpu | `-2*[hald-addon-stor] ... |-syslogd |-udevd `-vmtoolsd 这是最简用法,6*[getty]表示有6个getty进程,-c则禁用这种压缩模式。 # pstree -npu init(1)-+-migration/0(2) |-ksoftirqd/0(3) |-events/0(4) |-khelper(5) |-kthread(6)-+-kblockd/0(9) | |-kacpid(10) | |-kseriod(69) | |-pdflush(110) | |-pdflush(111) | |-kswapd0(112) | |-aio/0(113) | |-scsi_eh_0(590) | |-kjournald(674) | |-kpsmoused(1033) | |-kgameportd(1110) | |-kmirrord(1280) | `-vmmemctl(1577) |-udevd(739) |-vmtoolsd(1845) |-portmap(1940,daemon) |-rpc.statd(1951,statd) |-syslogd(2081) |-klogd(2090) |-acpid(2099) |-dbus-daemon(2109,messagebus) |-sshd(2124)-+-sshd(514)---bash(518)---pstree(617) | |-sshd(2354)---bash(2358)---sleep(495) | |-sshd(5980)---bash(6557) | `-sshd(30875)---sftp-server(30879) |-inetd(2169) |-avahi-daemon(2181,avahi)---avahi-daemon(2182) |-dhcdbd(2194) |-hald(2205,haldaemon)---hald-runner(2206,root)-+-hald-addon-inpu(2226) | |-hald-addon-acpi(2234,haldaemon) | |-hald-addon-stor(2235) | `-hald-addon-stor(2237) |-NetworkManager(2248) |-NetworkManagerD(2256) |-cron(2278) |-kdm(2297)-+-Xorg(2303) | `-kdm(2314)---kdm_greet(2348) |-getty(2325) |-getty(2328) |-getty(2329) |-getty(2330) |-getty(2331) |-getty(2332) `-dirmngr(8762) -p表示输出PID,-n表示按PID排序(缺省按进程名排序),-u表示当子进程的UID不同 于父进程的UID时在子进程后面的圆括号里显示子进程的UID。 hald(2205,haldaemon)表示PID为2205、UID为haldaemon。 # id haldaemon uid=105(haldaemon) gid=108(haldaemon) groups=108(haldaemon) # pstree -npu -al 2124 sshd,2124 |-sshd,514 | `-bash,518 | `-pstree,642 -npu -al 2124 |-sshd,2354 | `-bash,2358 | `-sleep,495 6000 |-sshd,5980 | `-bash,6557 `-sshd,30875 `-sftp-server,30879 -a表示显示命令行参数,-l表示不截断较长的行,结尾的2124表示以PID 2124为根显 示进程树(缺省以PID 1也就是init为根)。 # pstree -npu haldaemon hald(2205)---hald-runner(2206,root)-+-hald-addon-inpu(2226) |-hald-addon-acpi(2234,haldaemon) |-hald-addon-stor(2235) `-hald-addon-stor(2237) 结尾的haldaemon表示显示所有UID为haldaemon的进程及以之为根的进程树。 -h、-H 用来高亮显示某些进程链,在终端上才能看出效果,这里无法举例,有 时还是有点用的。可以试试: # pstree -H `pidof -s sftp-server` -npu 这将高亮显示sftp-server及其所有祖先进程。 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@nsfocus 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()。据我的经验, kd = kvm_open( KERN_NAMELIST, KERN_CORE, NULL, O_RDONLY, argv[0] ); kd = kvm_open( "/dev/ksyms", "/dev/mem", NULL, O_RDWR, argv[0] ); 此时可以使用kvm_read(),但我还是推荐使用kvm_kread() kd = kvm_open( NULL, NULL, NULL, O_RDONLY, NULL ); 此时强烈推荐使用kvm_kread() 13.2 如何在命令行上访问指定进程P、U两区,如何欺骗Solaris的ps A: scz@nsfocus 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)是怎么实现的 A: scz@nsfocus 2001-05-31 首先这是一个库函数,不是系统调用,否则应该是getexecname(2)。根据man手册得 知,返回execve( char * file, ... )第一形参的拷贝,注意不是argv[0]。返回值 可能是绝对路径,也可能是相对路径。 顺便说一句,对于Solaris,进程的argv[0]可以修改,但ps查看时用到的信息并不来 自argv[0],所以无法象Linux下那样通过修改argv[0]欺骗ps显示。 -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -o scztest scztest.c */ #include #include int main ( int argc, char * argv[] ) { printf( "getexecname() = %s\n", getexecname() ); return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- 下面是truss的简化输出 $ truss scztest execve("scztest", 0xFFBEFB94, 0xFFBEFB9C) argc = 1 open("/proc/self/auxv", O_RDONLY) = 3 read(3, "\0\007D8FFBEFFE3\0\007DE".., 152) = 152 $ 一个以前从未注意过的"/proc/self/auxv",man -s 4 proc 发现如下内容: 进程可以通过一个不可见的别名/proc/self访问自身对应的/proc//目录,所谓 不可见指"self"这个名字不会在ls(1)、getdents(2)、readdir(3C)操作/proc目录列 表时出现。 /proc//auxv -- auxv_t结构数组,包含进程执行时传递给动态链接器的初始值。 auxv_t结构定义在/usr/include/sys/auxv.h文件中。 在Solaris 7下 # cat /proc/1/auxv | od -A x -t x1 -v - 0000000 00 00 07 d8 ff be ff e0 00 00 07 de ff be ff f0 0000010 00 00 00 03 00 01 00 34 00 00 00 04 00 00 00 20 0000020 00 00 00 05 00 00 00 05 00 00 00 09 00 02 5f d8 0000030 00 00 00 07 ff 3b 00 00 00 00 00 08 00 00 03 00 0000040 00 00 00 06 00 00 20 00 00 00 07 d0 00 00 00 00 0000050 00 00 07 d1 00 00 00 00 00 00 07 d2 00 00 00 00 0000060 00 00 07 d3 00 00 00 00 00 00 07 d9 00 00 00 07 0000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000090 00 00 00 00 00 00 00 00 0000098 # 总共读取了0x98 == 152个字节 -------------------------------------------------------------------------- /usr/include/sys/auxv.h typedef struct { int a_type; union { long a_val; #ifdef __STDC__ void *a_ptr; #else char *a_ptr; #endif void (*a_fcn)(); } a_un; } auxv_t; #if defined(_SYSCALL32) typedef struct { int32_t a_type; union { int32_t a_val; caddr32_t a_ptr; caddr32_t a_fcn; } a_un; } auxv32_t; #endif /* _SYSCALL32 */ /* * The following aux vector provides a pointer to a null-terminated * path name, a copy of the path name passed to the exec() system * call but that has had all symlinks resolved (see resolvepath(2)). */ #define AT_SUN_EXECNAME 2014 /* exec() path name */ -------------------------------------------------------------------------- 参看resolvepath(2)、readlink(2)、realpath(3C)、getcwd(3C)了解更多关于符号 链接、相对路径、绝对路径的信息。 2014 == 0x07DE -------------------------------------------------------------------------- /usr/include/sys/user.h #define PSARGSZ 80 /* Space for exec arguments (used by ps(1)) */ #define MAXCOMLEN 16 /* <= MAXNAMLEN, >= sizeof (ac_comm) */ /* * __KERN_NAUXV_IMPL is defined as a convenience sizing mechanism * for the portions of the kernel that care about aux vectors. * * Applications that need to know how many aux vectors the kernel * supplies should use the proc(4) interface to read /proc/PID/auxv. * * This value should not be changed in a patch. */ #if defined(sparc) || defined(__sparc) #define __KERN_NAUXV_IMPL 19 #elif defined(i386) || defined(__i386) #define __KERN_NAUXV_IMPL 21 #endif /* sparc || _sparc */ typedef struct user { ... ... /* * Executable file info. */ auxv_t u_auxv[__KERN_NAUXV_IMPL]; /* aux vector from exec */ char u_psargs[PSARGSZ]; /* arguments from exec */ char u_comm[MAXCOMLEN + 1]; /* * Initial values of argc, argv and envp to main(), plus * the initial address of the aux vector, for /proc */ int u_argc; int u_envc; int u_auxvc; uintptr_t u_argv; uintptr_t u_envp; uintptr_t u_auxvp; ... ... } user_t; #include /* cannot include before user defined */ #ifdef _KERNEL #ifdef sun #define u (curproc->p_user) /* user is now part of proc structure */ #endif /* sun */ -------------------------------------------------------------------------- 19 * 8 == 152。我还有一点疑惑,看这里的头文件定义,并没有使用auxv32_t数组, 而是auxv_t数组,为什么结构大小为8,不懂。难道和/usr/bin/cat本身是32-bit应 用程序有关,又或者getexecname()的库函数实现区分了32-bit和64-bit模式?有一 点可以确认,在64-bit kernel mode中,使用的不是auxv32_t数组,结构大小不是8, 应该是16(考虑对齐问题)。 现在问题很显然了,getexecname(3C)打开了/proc/self/auxv,然后找到类似数据 00 00 07 de ff be ff f0 ~~~~~~~~~~~ ~~~~~~~~~~~ 这个是类型 这个是指针(属于进程地址空间的指针) 在1号进程0xffbefff0处,应该是execve()第一形参的一份拷贝,我们来验证之 # gdb init 1 <-- attach进程号1 GNU gdb 5.0 (gdb) x/s 0xffbefff0 0xffbefff0: "/sbin/init" <-- 验证成功 (gdb) q <-- detach进程 # ps -p 1 PID TTY TIME CMD 1 ? 0:29 init # ps的CMD显示应该取自U区的u_comm(也不是argv[0]),和getexecname()无关。 # gdb ../../../../../export/home/scz/src/scztest GNU gdb 5.0 (gdb) b main Breakpoint 1 at 0x10930 (gdb) r Starting program: /export/home/scz/src/../../../../../export/home/scz/src/scztest Breakpoint 1, 0x10930 in main () (gdb) x/s 0xffbeffda <-- 参看后面的步骤 0xffbeffda: "/export/home/scz/src/scztest" (gdb) c Continuing. getexecname() = /export/home/scz/src/scztest <-- 注意和ps的CMD显示不同 参看resolvepath(2) Program exited normally. "../"被处理掉了 (gdb) q # # pgrep scztest <-- 从另外一个伪终端上执行 26471 # ps -p 26471 PID TTY TIME CMD 26471 pts/1 0:00 scztest <-- 注意CMD显示和getexecname()返回值不同 # cat /proc/26471/auxv | od -A x -t x1 -v - 0000000 00 00 07 d8 ff be ff ca 00 00 07 de ff be ff da ... ... ~~~~~~~~~~~我们关心的指针 0000098 # 头文件/usr/include/sys/auxv.h里说明 /* * The following aux vector provides a pointer to a null-terminated * path name, a copy of the path name passed to the exec() system * call but that has had all symlinks resolved (see resolvepath(2)). */ 刚才测试结果为,冗余的"../"以及"./"被处理掉了,非冗余的或者无法智能解析的 "../"还是会留下,至于符号链接的情形,getexecname()并没有返回最终常规文件名, 还是把符号链接本身返回回来,这些都不是getexecname()的问题,填写auxv_t结构 数组的时候已经这样了。 如果程序启动后chdir(2)改动过当前目录,而getexecname()返回值是个相对路径, 就无法利用getcwd()拼接原始绝对路径。这点在getexecname(3C)手册页中提到了。 为了获得原始绝对路径,确认在main()函数中尽早调用getexecname(),根据返回值 是否已经是绝对路径决定是否调用getcwd()拼接(getcwd()返回的绝对路径结尾处没 有'/')。即使这样处理,可能得到的也并非理想的完全抛弃了"../"和"./"的绝对路 径名,参看realpath(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 FAQ ID - 2819 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/2819 下面是一个简单的演示程序,利用了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@nsfocus # 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: scz@nsfocus 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@nsfocus 下面来看一个简单的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@nsfocus 对于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 7.2 netstat -nap | grep "^tcp"直接可以看到端口所属的进程 netstat -na [] ={-t|--tcp} {-u|--udp} {-w|--raw} {-x|--unix} --ax25 --ipx --netrom 5) Debian 6.0 lsof -ni tcp:22 | grep LISTEN netstat -nap | grep :22 | grep LISTEN fuser -n tcp 22 | xargs ps -p 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 -static -Wall -pipe -g -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@nsfocus & microcat 2000-03-18 下面在x86/Linux Kernel 2.4.7-10上演示、讨论 -------------------------------------------------------------------------- /* * gcc -static -Wall -pipe -g -o myprog myprog.c */ #include #include int main ( int argc, char * argv[] ) { return( EXIT_SUCCESS ); } /* end of main */ -------------------------------------------------------------------------- [scz@ /home/scz/src]> gcc -static -Wall -pipe -g -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 -static -Wall -pipe -g -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@nsfocus & microcat 2002-09-13 18:00 下面在SARPC/Solaris 8 64-bit kernel mode上演示、讨论 -------------------------------------------------------------------------- /* * gcc -static -Wall -pipe -g -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@nsfocus 2002-09-13 20:45 这是一个x86/FreeBSD 4.5-RELEASE系统中利用proc获取绝对路径的例子 -------------------------------------------------------------------------- /* * gcc -static -Wall -pipe -g -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: scz@nsfocus 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: scz@nsfocus 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 */ -------------------------------------------------------------------------- 13.15 fork()+exec*()的exec*()返回-1时如何通知父进程 Q: Dieken@SMTH 2008-06-06 16:01:52 -------------------------------------------------------------------------- /* * fork()+exec*()的框架流程我可以控制 */ if ( ( pid = fork() ) < 0 ) { /* * show error message */ } else if ( pid > 0 ) { /* * 父进程需要知道子进程中exec*()是否返回-1,但不关心被执行的目标程序的 * 返回码。不能调用wait*()等待子进程,因为这将产生阻塞。不能用信号机制, * 因为我只能控制fork()+exec*()的框架流程,不能控制整个父进程(没有源代 * 码),安装信号句柄的代码不在我的控制之下,不能随意安装新的信号句柄, * 以免产生冲突。 */ } else { /* * 子进程 */ if ( -1 == setsid() ) { exit( 0 ); } if ( -1 == chdir( "/" ) ) { exit( 0 ); } if ( -1 == close( 0 ) ) { exit( 0 ); } if ( -1 == close( 1 ) ) { exit( 0 ); } if ( -1 == close( 2 ) ) { exit( 0 ); } /* * exec*()的第一形参我无法控制,假设是任意可执行程序 */ if ( -1 == exec*( ... ) ) { /* * 如果exec*()成功的话,流程永远不会到达此处。 */ exit( 0 ); } } -------------------------------------------------------------------------- A: vonNeumann@SMTH 2008-06-06 16:23:14 一个备选方案: -------------------------------------------------------------------------- int fds[2]; pid_t pid; char c; ssize_t n; pipe( fds ); if ( 0 == ( pid = fork() ) { /* * 子进程 */ close( fds[0] ); fcntl( fds[1], F_SETFD, FD_CLOEXEC ); exec*( ... ); write( fds[1], "", 1 ); /* * 绕过atexit()机制 */ _exit( 127 ); } /* * 父进程 */ close( fds[1] ); /* * 如果exec*()成功,read()应该返回0。参APUE 14.2小节。 */ n = read( fds[0], &c, 1 ); close( fds[0] ); /* * 如果n非零,意味着exec*()返回-1了。 */ if ( n ) { fprintf( stderr, "Ooops ...\n" ); waitpid( pid, NULL, 0 ); return( -1 ); } return( pid ); -------------------------------------------------------------------------- 13.16 wait3/wait4/getrusage/waitpid A: scz@nsfocus 2013-11-15 14:17 -------------------------------------------------------------------------- SYNOPSIS #include #include #include #include pid_t wait3 ( int *status, int options, struct rusage *rusage ); pid_t wait4 ( pid_t pid, int *status, int options, struct rusage *rusage ); Feature Test Macro Requirements for glibc (see feature_test_macros(7)): wait3() _BSD_SOURCE || _XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED wait4() _BSD_SOURCE DESCRIPTION 这两个函数过时了,建议用waitpid()、waitid()替换它们。 wait3()、wait4()与waitpid()类似,区别在于它们通过形参rusage返回子进程 的资源利用信息。如果不考虑形参rusage: wait3( status, options, rusage ); 等价于 waitpid( -1, status, options ); wait4( pid, status, options, rusage ); 等价于 waitpid( pid, status, options ); 换句话说,wait3()等待所有子进程,wait4()等待指定子进程,参看wait(2)手 册页。 如果rusage不为NULL,通过它返回子进程的资源利用信息。参看getrusage(2)手 册页。 RETURN VALUE 同waitpid(2)。 成功时返回状态发生变化的子进程的PID。如果通过options指定了WNOHANG并且 正在等待一或多个子进程,但这些子进程状态没有发生变化,返回0。发生错误 时返回-1。 ERRORS 同waitpid(2)。 ECHILD pid指定的进程不存在或不是当前进程的子进程。如果当前进程将SIGCHLD信 号句柄设置成SIG_IGN,就会发生这种情况。参看waitpid(2)的Linux Notes 小节,那里讨论了一个线程等待另一个线程创建的子进程,问题颇多,不建 议这么干。 EINTR 未指定WNOHANG并且收到未被阻塞的信号或SIGCHLD信号。参看signal(7)。 EINVAL 指定了无效options NOTES 现在已经不要求包含,但这样做会增加可移植性。 中定义了rusage结构,它有一个成员类型是struct timeval,该类型在 中定义。 wait3()是库函数,wait4()是系统调用,前者是对后者的封装。 SEE ALSO fork(2), getrusage(2), sigaction(2), signal(2), wait(2), signal(7) -------------------------------------------------------------------------- 14. 一些小工具的使用 14.0 paste命令 Q: 1.txt如下: username_0 username_1 username_2 2.txt如下: password_0 password_1 password_2 我想得到3.txt: username_0:password_0 username_1:password_1 username_2:password_2 如何做? A: paste -d':' 1.txt 2.txt > 3.txt 14.1 如何在命令行上进行8进制、10进制、16进制之间的转换 A: scz@nsfocus 2001-05-30 23:47 下列命令完成65535到0xFFFF的转换: $ echo c 65535 16o p c | /usr/bin/dc FFFF 下列命令完成0x3934C024到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@nsfocus 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 Infodoc ID - 19379 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/19379 首先理解如下概念 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@nsfocus 在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 Infodoc ID - 18295 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/18295 下面的脚本允许用户只在本地文件系统上查找文件,而不会跨越到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 Infodoc ID - 16163 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/16163 下面举例演示如何不在NFS文件系统上查找文件,参看两个Bug ID(1260889 4060648) http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=bug/utility/program/1260889 http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=bug/utility/file/4060648 # 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 Bug ID - 4060648 http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=bug/utility/file/4060648 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@nsfocus 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@nsfocus 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 反汇编 A: wangdb@nsfocus -------------------------------------------------------------------------- #! /bin/sh # File : bindump.sh # Author : wangdb@nsfocus # if [ "$1" = "" -o "$2" = "" ] ; then if [ "$1" = "" ] || [ "$2" = "" ] ; then echo Usage: $0 \ \
exit 1 fi object=$1 shift 1 rm -f /tmp/..gdb_tmp_cmd_file.$$ rm -f /tmp/..gdb_tmp_cmd_file.tmp.$$ echo disas $* > /tmp/..gdb_tmp_cmd_file.$$ echo quit >> /tmp/..gdb_tmp_cmd_file.$$ gdb -q -x /tmp/..gdb_tmp_cmd_file.$$ ${object} > /tmp/..gdb_tmp_cmd_file.tmp.$$ cat /tmp/..gdb_tmp_cmd_file.tmp.$$ rm -f /tmp/..gdb_tmp_cmd_file.$$ rm -f /tmp/..gdb_tmp_cmd_file.tmp.$$ #End -------------------------------------------------------------------------- 可以先nm -g 获取函数名,然后反汇编查看。 14.6 wget指定代理 A: tk@nsfocus wget本身没有专门设置代理的命令行参数,但是有一个"-e"参数,可以在命令行上指 定一个原本出现在".wgetrc"中的设置。于是可以变相在命令行上指定代理: wget -e "http_proxy=:" http://www.google.com 另一个办法就是先设置环境变量: set http_proxy=: 除了http_proxy,wget还支持ftp_proxy环境变量: wget -e "ftp_proxy=:" ftp://... 14.7 curl使用技巧 A: tk@nsfocus tk@nsfocus 2006-07-31 14:33 1) 保持远端文件名 curl -O http://1.2.3.4/xxx 2) 使用HTTP代理 curl -O -x 8.8.8.8:8888 http://1.2.3.4/xxx curl可以用GET方式通过HTTP代理访问FTP。 3) 断点续传 curl -O -C - http://1.2.3.4/xxx -C后面可以指定从文件某处偏移(字节单位)开始下载,如果不指定具体偏移而用-, 则根据已经下载的文件大小自动续传。 4) 批量下载 curl -O http://1.2.3.4/abcd[01-99].rar curl -O ftp://1.2.3.4/1234[a-f].rar 5) 使用Cookie curl -b "PHPSESSID=2f3b822847a1e7d443faf25ed126384f" http://1.2.3.4/xxx -b参数可以直接指定Cookie,也可以指定Cookie文件。Cookie文件可以由-c参数生成: curl -c passport.txt http://1.2.3.4/login.php -d "user=root&pass=secure" curl -b passport.txt http://1.2.3.4/download.php?file=private.zip A: zd 假设服务端有一个upload.php: move_uploaded_file($_FILES['localfile']['tmp_name'],$_POST['remotefile']); 客户端如下: -------------------------------------------------------------------------- PHP Upload Shell
Remote file:
Local file:
-------------------------------------------------------------------------- 使用curl上传: curl -x "ip:port" -s -F 'localfile=@/tmp/some.php' -F 'remotefile=X:\...\some.php' http://.../upload.php 14.8 Debian上有什么类似Windows上fc.exe的工具 A: 试试"vbindiff" 14.9 iconv命令 A: iconv --list curl -v -s http://... | iconv -cs -f UTF8 -t GB2312 14.10 如何计算MD5、SHA-1 A: $ echo -n "123456" | md5sum e10adc3949ba59abbe56e057f20f883e - $ echo -n "123456" | shasum 7c4a8d09ca3762af61e59520943dc26494f8941b - $ echo -n "123456" | openssl md5 e10adc3949ba59abbe56e057f20f883e $ echo -n "123456" | openssl sha1 7c4a8d09ca3762af61e59520943dc26494f8941b $ md5sum -b /bin/ls 323bfe62898cd67edd703464c1cee242 */bin/ls $ shasum -b /bin/ls e35f41d4f1d356219b3e1c6d306d3dbf7ac7e2d0 */bin/ls $ openssl md5 /bin/ls MD5(/bin/ls)= 323bfe62898cd67edd703464c1cee242 $ openssl sha1 /bin/ls SHA1(/bin/ls)= e35f41d4f1d356219b3e1c6d306d3dbf7ac7e2d0 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@nsfocus 非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 FAQ ID - 3122 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/3122 从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@nsfocus 这个函数比较新,还有另外几个,比如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 FAQ ID - 1531 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/1531 编译时指定-KPIC选项导致编译器产生"位置无关"的目标文件 cc -c -KPIC file1.c cc -c -KPIC file2.c 然后经由编译器链接目标文件,指定-G和-o选项 cc -G -o libname.so file1.o file2.o -G选项通知链接器生成动态链接库,-o指定即将创建的动态链接库名。".so"是 "shared object"的意思。 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@nsfocus 不要重启动,立即用/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 FAQ ID - 2750 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/2750 下面即将演示如何利用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 . $ A: Ulrich Drepper # /lib/i386-linux-gnu/libc.so.6 GNU C Library (Debian EGLIBC 2.13-38) stable release version 2.13, by Roland McGrath et al. Copyright (C) 2011 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 4.4.7. Compiled on a Linux 3.2.35 system on 2012-12-30. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: . -------------------------------------------------------------------------- /* * gcc -Wall -pipe -O3 -s -o glibcver glibcver.c */ #include #include int main ( void ) { puts( gnu_get_libc_version() ); return( 0 ); } -------------------------------------------------------------------------- $ gcc -Wall -pipe -O3 -s -o glibcver glibcver.c $ ./glibcver 2.13 $ ldd --version ldd (Debian EGLIBC 2.13-38) 2.13 Copyright (C) 2011 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. Written by Roland McGrath and Ulrich Drepper. D: scz@nsfocus 2014-03-21 09:51 "ldd --version"最不可靠,/usr/bin/ldd是一个脚本,里面有一句: echo 'ldd (Debian EGLIBC 2.13-38) 2.13' # dpkg -S /usr/bin/ldd libc-bin: /usr/bin/ldd # dpkg -S /lib/ld-linux.so.2 libc6:i386: /lib/ld-linux.so.2 ldd不属于libc6:i386,它是libc-bin包里的,有时候这两个包并不同步升级,ldd给 出的信息就不可信了。 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@nsfocus 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 FAQ ID - 3433 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=faqs/3433 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-05-31 13:05 对于Win32、AIX来说,你所期待的效果是缺省行为,但其他操作系统未必如此。对于 Solaris、FreeBSD、Linux,使用gnu ld生成libfoo.so时应指定-Bsymbolic。缺省情 况下没有指定-Bsymbolic,此时prog.c中的func1()被func2()调用。 A: Mars Rullgard 2003-05-30 17:09 如果libfoo.so没有使用prog.c中符号(变量、函数),使用gnu ld链接prog.c时不要 指定-rdynamic或者-export-dynamic,这样prog.c中func1符号不被导出,func2()就 不会调用prog.c中的func1()。如果还是不能解决问题,设法使prog.c中的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 16.16 编译源码时configure相关问题 D: 编译源码时configure出错,检查所有configure.in,包括子目录里,确认出错原因。 针对出错原因做过相应处理之后,应删除config.cache,否则会对再次configure带 来困挠。 16.17 编译GDB时遭遇"configure: error: no termcap library found" Q: 在QEMU模拟的PowerPC上安装Debian,编译GDB 6.0源码时遭遇"configure: error: no termcap library found"。在库搜索路径上确实没有找到libtermcap.*,用 "apt-cache search termcap"也没有找到合适的安装包。 A: qfp@nsfocus libtermcap.*属于被废弃的库,现在基本转用libncurses.*。出现上述提示,可能是 由于未安装libncurses.*,继而寻找更古老的libtermcap.*所致。安装libncurses.* 应该就能解决问题: apt-get install libncurses5-dev 16.18 用gcc-2.95编译程序时遭遇"/usr/bin/ld: crt1.o: No such file: No such file or directory" Q: 我用"apt-get install gcc-2.95"安装了gcc-2.95,然后写了一个简单的C程序测试 gcc-2.95,结果报错: $ gcc-2.95 -o test test.c /usr/bin/ld: crt1.o: No such file: No such file or directory collect2: ld returned 1 exit status A: apt-get install libc6-dev 16.19 在链接时遭遇"undefined reference to tputs" Q: gcc -o ... some.o -lreadline 报了个错: readline/display.c:...: undefined reference to `tputs' A: gcc -o ... some.o -lncurses -lreadline 16.20 Linux系统调用 D: 查看当前Linux内核版本: $ uname -r 2.6.18-4-686 从"www.kernel.org"下载相应内核源码: http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.18.tar.xz 系统调用号在"include/asm-i386/unistd.h"中定义: #define __NR_gettid 224 通常系统调用foo()对应的内核函数是sys_foo(),参看: arch/i386/kernel/syscall_table.S 数组sys_call_table[]用于存放这些内核函数指针。 以前系统调用是通过"int 0x80"进行的。eax对应系统调用号,6个形参依次对应: ebx ecx edx esi edi ebp -------------------------------------------------------------------------- $ gdb /usr/bin/col (gdb) catch syscall open Catchpoint 1 (syscall 'open' [5]) (gdb) r Starting program: /usr/bin/col Catchpoint 1 (call to syscall 'open'), 0xb7ffab54 in ?? () from /lib/ld-linux.so.2 (gdb) x/i $eip-2 0xb7ffab52: int $0x80 (gdb) i r eax ebx ecx edx esi edi ebp eax 0xffffffda -38 ebx 0xbfffd600 -1073752576 ecx 0x0 0 edx 0x0 0 esi 0x1 1 edi 0xb7fe3848 -1208076216 ebp 0xbfffd5e8 0xbfffd5e8 (gdb) x/s $ebx 0xbfffd600: "/usr/local/lib/tls/i686/sse2/cmov/libc.so.6" (gdb) x/5i $eip 0xb7ffab54: pop %ebx 0xb7ffab55: cmp $0xfffff001,%eax 0xb7ffab5a: jae 0xb7ffab5d 0xb7ffab5c: ret 0xb7ffab5d: call 0xb7ffb887 (gdb) ni 0xb7ffab54 in ?? () from /lib/ld-linux.so.2 (gdb) i r eax eax 0xfffffffe -2 -------------------------------------------------------------------------- gdb的"catch syscall ..."断下来时,eip位于"int 0x80"之后,但实际上系统调用 尚未进行,eax里保存的不是返回值。ni之后,eip未变,系统调用已经返回,eax里 保存返回值。就本例而言,由于"/usr/local/lib/tls/i686/sse2/cmov/libc.so.6" 不存在,故eax不是有效值。"man 2 open"得知这个系统调用的第一形参是pathname, 在gdb中查看第一形参。 高版本的Intel芯片提供sysenter/sysexit指令,AMD芯片提供syscall/sysret指令。 后来的Linux系统调用开始使用这些指令,以提高效率。 系统调用gettimeofday()被频繁使用,如果有一个高效实现会降低系统负载。办法之 一是将当前时间写在一个固定位置,其所在内存页被映射到所有进程空间,每次时钟 中断都会自动更新这个固定位置的数据。进程只需要读这个固定位置的数据即可获取 当前时间,不需要跨越用户态、内核态的边界,不需要真实的系统调用。还有一些数 据,比如当前pid,为获取它也不需要真实的系统调用。这些系统调用称为vsyscall。 从Linux 2.5.53开始,有一个固定页,vsyscall page,由内核负责填充有效内容。 在内核初始化阶段调用sysenter_setup(),它会建立一个不可写的页,参看: arch/i386/kernel/sysenter.c C库函数通过跳到"vsyscall page"中某个固定地址进行快速系统调用。该页内容是ELF 格式的,从Linux 2.5.74开始该页被命名为"linux-gate.so.1"。 $ ldd `which col` linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7e60000) /lib/ld-linux.so.2 (0x80000000) 在没有ASLR机制的年代,vsyscall page被映射到固定地址(0xffffe000-0xffffefff)。 从Linux 2.6.18开始,该页被映射到一个随机地址: $ setarch `uname -m` -R cat /proc/self/maps | grep vdso b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] 参看"arch/i386/kernel/vsyscall-sysenter.S",了解vsyscall page的组成。 C库函数调用__kernel_vsyscall()进行系统调用,其位于vsyscall page中。内核通 过ELF Auxiliary Vectors中的AT_SYSINFO将__kernel_vsyscall()的地址传递给用户 态进程。也可以通过GS:0x10获取__kernel_vsyscall()的地址,关于这点,参看: 《在GDB里如何查看GS:0x10的内容》 在当前进程的栈中寻找AT_SYSINFO很不方便,因此libc.so被加载时,会从栈中复制 AT_SYSINFO的值到tcbhead_t.sysinfo,后者可以用GS:0x10访问。 -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o vsyscall_demo vsyscall_demo.c */ #include int tid; int main ( int argc, char * argv[], char * envp[] ) { asm \ (" \ movl $224, %eax ;\ call *%gs:0x10 ;\ movl %eax, tid ;\ "); printf( "tid is %d\n", tid ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o vsyscall_demo vsyscall_demo.c */ #include int main ( int argc, char * argv[], char * envp[] ) { int tid; asm \ ( \ " \ movl $224, %%eax ;\ call *%%gs:0x10 ;\ movl %%eax, %0 ;\ " :"=m"(tid) ); printf( "tid is %d\n", tid ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gdb ./vsyscall_demo (gdb) catch syscall gettid Catchpoint 1 (syscall 'gettid' [224]) (gdb) r Starting program: /tmp/vsyscall_demo Catchpoint 1 (call to syscall 'gettid'), 0xb7fe4410 in ?? () (gdb) x/i $eip-2 0xb7fe440e: jmp 0xb7fe4403 (gdb) i r eax eax 0xffffffda -38 (gdb) ni 0xb7fe4410 in ?? () (gdb) i r eax eax 0xfec 4076 -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o vsyscall_demo vsyscall_demo.c */ #include int main ( int argc, char * argv[], char * envp[] ) { int pid; asm \ ( \ " \ movl $20, %%eax ;\ int $0x80 ;\ movl %%eax, %0 ;\ " :"=m"(pid) ); printf( "pid is %d\n", pid ); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gdb ./vsyscall_demo (gdb) catch syscall getpid Catchpoint 1 (syscall 'getpid' [20]) (gdb) r Starting program: /tmp/vsyscall_demo Catchpoint 1 (call to syscall 'getpid'), 0x0804838d in ?? () (gdb) x/i $eip-2 0x804838b: int $0x80 (gdb) i r eax eax 0xffffffda -38 (gdb) ni 0x0804838d in ?? () (gdb) i r eax eax 0x100c 4108 -------------------------------------------------------------------------- 16.21 linux-gate.so.1是什么东西 Q: $ ldd `which col` linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7e60000) /lib/ld-linux.so.2 (0x80000000) $ find /lib -name linux-gate.so.1 -type f -print $ find /usr/lib -name linux-gate.so.1 -type f -print $ 用ldd时注意到有一个linux-gate.so.1,但在文件系统中找不到,这是神马东西? A: linux-gate.so.1是一个"Virtual Dynamic Shared Object",即vdso,由内核导出, 被映射到每个进程的地址空间中。vsyscall位于该页。参看: 《Linux系统调用》 在没有ASLR机制的年代,vsyscall page被映射到固定地址(0xffffe000-0xffffefff)。 从Linux 2.6.18开始,该页被映射到一个随机地址: $ cat /proc/self/maps | grep vdso b7eef000-b7ef0000 r-xp b7eef000 00:00 0 [vdso] 过去AT_SYSINFO_EHDR指向vsyscall page起始地址,引入ASLR之后,AT_SYSINFO_EHDR 的值不可信了。但AT_SYSINFO仍指向随机化后的__kernel_vsyscall()。 -------------------------------------------------------------------------- /* * gcc-3.3 -Wall -pipe -O3 -s -o get_vsyscall_page get_vsyscall_page.c */ #include #include #include #include #include #include #include #include unsigned int get_auxv ( Elf32_auxv_t *auxv, unsigned int type ) { unsigned int value = 0xffffffff; for ( ; AT_NULL != auxv->a_type; auxv++ ) { if ( type == auxv->a_type ) { value = auxv->a_un.a_val; break; } } return( value ); } /* end of get_auxv */ int main ( int argc, char * argv[], char * envp[] ) { Elf32_auxv_t *auxv; Elf32_Ehdr *so; Elf32_Shdr *sh; unsigned int size; unsigned char *buf = NULL; int f = -1; while ( NULL != *envp++ ); auxv = ( Elf32_auxv_t * )envp; /* * 从AT_SYSINFO的值推测vsyscall page的起始地址,这是一个经验搞法,不可 * 靠。 */ so = ( Elf32_Ehdr * )( get_auxv( auxv, AT_SYSINFO ) & ~0xFFF ); sh = ( Elf32_Shdr * )( ( unsigned char * )so + so->e_shoff ); /* * 这是山寨搞法。假设Section header table位于最未尾。 */ size = so->e_shoff + ( so->e_shentsize * so->e_shnum ); buf = ( unsigned char * )calloc( size, 1 ); if ( NULL == buf ) { perror( "calloc() failed" ); goto main_exit; } memcpy( buf, so, size ); /* * write( 1, buf, size ); * * $ ./get_vsyscall_page > ... */ f = open( "/tmp/vsyscall_page.so", O_CREAT | O_WRONLY, S_IRWXU ); if ( -1 == f ) { perror( "open() failed" ); goto main_exit; } write( f, buf, size ); main_exit: if ( -1 != f ) { close( f ); f = -1; } if ( NULL != buf ) { free( buf ); buf = NULL; } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- vsyscall page的内容是ELF格式的,可以调用getpagesize()获取页大小,转储该页。 get_vsyscall_page.c取了ELF文件的精确大小,这是出于演示目的,不是必要的。 $ ./get_vsyscall_page $ ls -l /tmp/vsyscall_page.so -rwx------ 1 root root 2216 05-10 17:49 /tmp/vsyscall_page.so* $ file -b /tmp/vsyscall_page.so ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped $ readelf -e /tmp/vsyscall_page.so ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Intel 80386 Version: 0x1 Entry point address: 0xffffe400 Start of program headers: 52 (bytes into file) Start of section headers: 1696 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 4 Size of section headers: 40 (bytes) Number of section headers: 13 Section header string table index: 12 Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .hash HASH ffffe0b4 0000b4 000038 04 A 2 0 4 [ 2] .dynsym DYNSYM ffffe0ec 0000ec 000090 10 A 3 5 4 [ 3] .dynstr STRTAB ffffe17c 00017c 000056 00 A 0 0 1 [ 4] .gnu.version VERSYM ffffe1d2 0001d2 000012 02 A 2 0 2 [ 5] .gnu.version_d VERDEF ffffe1e4 0001e4 000038 00 A 3 2 4 [ 6] .text PROGBITS ffffe400 000400 000060 00 AX 0 0 32 [ 7] .note NOTE ffffe460 000460 000018 00 A 0 0 4 [ 8] .eh_frame_hdr PROGBITS ffffe478 000478 000024 00 A 0 0 4 [ 9] .eh_frame PROGBITS ffffe49c 00049c 00010c 00 A 0 0 4 [10] .dynamic DYNAMIC ffffe5a8 0005a8 000078 08 WA 3 0 4 [11] .useless PROGBITS ffffe620 000620 00000c 04 WA 0 0 4 [12] .shstrtab STRTAB 00000000 00062c 000073 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x000000 0xffffe000 0xffffe000 0x0062c 0x0062c R E 0x1000 DYNAMIC 0x0005a8 0xffffe5a8 0xffffe5a8 0x00078 0x00078 R 0x4 NOTE 0x000460 0xffffe460 0xffffe460 0x00018 0x00018 R 0x4 GNU_EH_FRAME 0x000478 0xffffe478 0xffffe478 0x00024 0x00024 R 0x4 Section to Segment mapping: Segment Sections... 00 .hash .dynsym .dynstr .gnu.version .gnu.version_d .text .note .eh_frame_hdr .eh_frame .dynamic .useless 01 .dynamic 02 .note 03 .eh_frame_hdr $ readelf -s /tmp/vsyscall_page.so Symbol table '.dynsym' contains 9 entries: Num: Value Size Type Bind Vis Ndx Name 0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: ffffe400 0 SECTION LOCAL DEFAULT 6 2: ffffe478 0 SECTION LOCAL DEFAULT 8 3: ffffe49c 0 SECTION LOCAL DEFAULT 9 4: ffffe620 0 SECTION LOCAL DEFAULT 11 5: ffffe400 20 FUNC GLOBAL DEFAULT 6 __kernel_vsyscall@@LINUX_2.5 6: 00000000 0 OBJECT GLOBAL DEFAULT ABS LINUX_2.5 7: ffffe440 7 FUNC GLOBAL DEFAULT 6 __kernel_rt_sigreturn@@LINUX_2.5 8: ffffe420 8 FUNC GLOBAL DEFAULT 6 __kernel_sigreturn@@LINUX_2.5 $ objdump -d -j .text /tmp/vsyscall_page.so /tmp/vsyscall_page.so: file format elf32-i386 Disassembly of section .text: ffffe400 <__kernel_vsyscall>: ffffe400: 51 push %ecx ffffe401: 52 push %edx ffffe402: 55 push %ebp ffffe403: 89 e5 mov %esp,%ebp ffffe405: 0f 34 sysenter ffffe407: 90 nop ffffe408: 90 nop ffffe409: 90 nop ffffe40a: 90 nop ffffe40b: 90 nop ffffe40c: 90 nop ffffe40d: 90 nop ffffe40e: eb f3 jmp ffffe403 <__kernel_vsyscall+0x3> ffffe410: 5d pop %ebp ffffe411: 5a pop %edx ffffe412: 59 pop %ecx ffffe413: c3 ret ffffe414: 90 nop ... ffffe41f: 90 nop ffffe420 <__kernel_sigreturn>: ffffe420: 58 pop %eax ffffe421: b8 77 00 00 00 mov $0x77,%eax ffffe426: cd 80 int $0x80 ffffe428: 90 nop ... ffffe43f: 90 nop ffffe440 <__kernel_rt_sigreturn>: ffffe440: b8 ad 00 00 00 mov $0xad,%eax ffffe445: cd 80 int $0x80 ffffe447: 90 nop ... ffffe45f: 90 nop 可以不用get_vsyscall_page.c转储vsyscall page: $ setarch `uname -m` -R cat /proc/self/maps | grep vdso b7fe4000-b7fe5000 r-xp b7fe4000 00:00 0 [vdso] $ echo 0xb7fe4000 | awk '{printf "%u\n", $1/4096;}' 753636 $ setarch `uname -m` -R dd if=/proc/self/mem of=/tmp/vsyscall_page.dd bs=4096 skip=753636 count=1 $ file -b /tmp/vsyscall_page.dd $ readelf -e /tmp/vsyscall_page.dd $ readelf -s /tmp/vsyscall_page.dd $ objdump -d -j .text /tmp/vsyscall_page.dd 16.22 快速获取.so的导出函数 Q: $ ldd `which id` linux-gate.so.1 => (0xffffe000) libselinux.so.1 => /lib/libselinux.so.1 (0xb7fd3000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7e8d000) libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7e88000) /lib/ld-linux.so.2 (0x80000000) $ file -b /lib/i686/cmov/libdl-2.11.2.so ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped $ nm -g /usr/lib/libdl.so nm: /usr/lib/libdl.so: no symbols 为什么"nm -g"没有看到想像中的dlopen? A: .so中有两套符号体系,一套是.symtab和.strtab,姑且称之为常规符号,另一套是 .dynsym和.dynstr,姑且称之为动态符号,关于这4个section参看ELF文件格式。 常规符号出于调试目的而存在,常常包含静态函数名这类信息,动态符号并不包含这 些。动态加载器从不使用常规符号,只使用动态符号。strip只会移除常规符号,不 会移除动态符号。"nm -g"寻找的是常规符号,对于"stripped .so",当然没有输出。 "nm -D"用于显示动态符号: $ nm -D /usr/lib/libdl.so | grep " T " 000013c0 T dladdr 00001410 T dladdr1 00000cb0 T dlclose 00001160 T dlerror 000014a0 T dlinfo 00001770 T dlmopen 00000b00 T dlopen 000018e0 T dlopen 00000cf0 T dlsym T表示这些符号位于.text section,并且是global (external)的。 第二种办法: $ readelf -s /usr/lib/libdl.so | grep "FUNC GLOBAL" Symbol table '.dynsym' contains 40 entries: Num: Value Size Type Bind Vis Ndx Name 1: 00000000 0 FUNC GLOBAL DEFAULT UND _dl_vsym@GLIBC_PRIVATE (7) 2: 00000000 0 FUNC GLOBAL DEFAULT UND strerror@GLIBC_2.0 (8) 5: 00000000 0 FUNC GLOBAL DEFAULT UND __dcgettext@GLIBC_2.0 (8) 6: 00000000 0 FUNC GLOBAL DEFAULT UND calloc@GLIBC_2.0 (8) 8: 00000000 0 FUNC GLOBAL DEFAULT UND __asprintf@GLIBC_2.1 (9) 9: 00000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.0 (8) 11: 00000000 0 FUNC GLOBAL DEFAULT UND _dl_sym@GLIBC_PRIVATE (7) 12: 00000000 0 FUNC GLOBAL DEFAULT UND _dl_addr@GLIBC_PRIVATE (7) 13: 00000000 0 FUNC GLOBAL DEFAULT UND _dl_rtld_di_serinfo@GLIBC_PRIVATE (10) 14: 00000000 0 FUNC GLOBAL DEFAULT UND strcpy@GLIBC_2.0 (8) 24: 000013c0 68 FUNC GLOBAL DEFAULT 13 dladdr@@GLIBC_2.0 25: 00001770 120 FUNC GLOBAL DEFAULT 13 dlmopen@@GLIBC_2.3.4 27: 00000b00 103 FUNC GLOBAL DEFAULT 13 dlopen@@GLIBC_2.1 28: 000018e0 100 FUNC GLOBAL DEFAULT 13 dlopen@GLIBC_2.0 29: 00001410 141 FUNC GLOBAL DEFAULT 13 dladdr1@@GLIBC_2.3.3 30: 00000cf0 146 FUNC GLOBAL DEFAULT 13 dlsym@@GLIBC_2.0 35: 00000cb0 63 FUNC GLOBAL DEFAULT 13 dlclose@@GLIBC_2.0 37: 000014a0 119 FUNC GLOBAL DEFAULT 13 dlinfo@@GLIBC_2.3.3 38: 00001160 597 FUNC GLOBAL DEFAULT 13 dlerror@@GLIBC_2.0 第三种办法: $ objdump -T /usr/lib/libdl.so | grep "g DF" 000013c0 g DF .text 00000044 GLIBC_2.0 dladdr 00001770 g DF .text 00000078 GLIBC_2.3.4 dlmopen 00000b00 g DF .text 00000067 GLIBC_2.1 dlopen 000018e0 g DF .text 00000064 (GLIBC_2.0) dlopen 00001410 g DF .text 0000008d GLIBC_2.3.3 dladdr1 00000cf0 g DF .text 00000092 GLIBC_2.0 dlsym 00000cb0 g DF .text 0000003f GLIBC_2.0 dlclose 000014a0 g DF .text 00000077 GLIBC_2.3.3 dlinfo 00001160 g DF .text 00000255 GLIBC_2.0 dlerror -t 显示常规符号 -T 显示动态符号 16.23 -fPIC/-fpic/-fPIE/-fpie的区别 Q: 生生DSO时,有人用-fPIC,有人用-fpic,到底用哪个? A: scz@nsfocus 2013-11-18 09:27 参看: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html -------------------------------------------------------------------------- -fpic 产生适用于DSO的位置无关代码(position-independent code),这种代码通过全 局偏移表(GOT)访问所有的固定地址。加载阶段由dynamic loader负责解析GOT表 项。每种架构支持的GOT表项数目有上限,如果因为使用-fpic导致需要的GOT表 项数目超过上限,在链接阶段linker会报错,指出-fpic无法正常工作,此时应 该换用-fPIC。 x86架构的GOT表项数目没有上限。 位置无关代码需要特殊的支持,仅能工作于某些架构,x86支持位置无关代码。 指定该选项时,宏_pic_和_PIC_被定义成1。 -fPIC 基本同-fpic,但没有GOT表项数目上限。对于m68k、PowerPC、SPARC,-fPIC的 内部实现有别于-fpic,前者会占用更多空间,后者更紧凑。对于x86,二者无区 别。 指定该选项时,宏_pic_和_PIC_被定义成2。 -fpie -fPIE 类似-fpic和-fPIC,但产生的位置无关代码只能用于主程序,不能用于DSO。如 果链接阶段指定了-pie,编译阶段就需要指定-fpie或-fPIE。 指定-fpie时,宏_pie_和_PIE_被定义成1。 指定-fPIE时,宏_pie_和_PIE_被定义成2。 -------------------------------------------------------------------------- 生成DSO时,对可移植性有完美主义倾向的人就指定-fPIC好了。 16.25 用sprof剖析共享库 D: scz@nsfocus 2013-11-19 14:24 -------------------------------------------------------------------------- /* * gcc-4.7 -fPIC -Wall -pipe -O3 -g3 -c -o foo.o foo.c -pthread * ld -m elf_i386 -shared -soname libfoo.so -o libfoo.so foo.o -lpthread * * or * * gcc-4.7 -fPIC -shared -Wl,-soname,libfoo.so -Wl,-m,elf_i386 -Wall -pipe -O3 -g3 -o libfoo.so foo.c -pthread * * 为什么前者生成的DSO更小? */ #include #include #include static unsigned char magic_buf[128] = { 0x74 }; static unsigned int magic_int = 0x51201314; void shared_func ( unsigned int i ) { unsigned int esp; asm \ ( \ " \ movl %%esp, %0 ;\ " :"=m"(esp) ); printf ( "\n" "0x%08X [%u]\n" "&magic_buf = 0x%08X\n" "&magic_int = 0x%08X\n" "&errno = 0x%08X\n" "esp = 0x%08X\n", ( unsigned int )pthread_self(), i, ( unsigned int )&magic_buf, ( unsigned int )&magic_int, ( unsigned int )&errno, esp ); return; } /* end of shared_func */ -------------------------------------------------------------------------- /* * gcc-4.7 -Wl,-rpath,'$ORIGIN' -Wall -pipe -O3 -g3 -o bar bar.c -pthread -L. -lfoo */ #include #include #include #include extern void shared_func ( unsigned int ); static void * run_so ( void *arg ) { shared_func( ( unsigned int )arg ); return( NULL ); } /* end of run_so */ int main ( int argc, char * argv[] ) { unsigned int thread_num = 3, i, esp; pthread_t *tid = NULL; if ( argc > 1 ) { thread_num = ( unsigned int )strtoul( argv[1], NULL, 0 ); } if ( NULL == ( tid = ( pthread_t * )calloc( thread_num, sizeof( pthread_t ) ) ) ) { perror( "calloc() failed" ); exit( -1 ); } for ( i = 0; i < thread_num; i++ ) { pthread_create( &tid[i], NULL, &run_so, ( void * )i ); } asm \ ( \ " \ movl %%esp, %0 ;\ " :"=m"(esp) ); printf ( "\n" "0x%08X [-1]\n" "&errno = 0x%08X\n" "esp = 0x%08X\n", ( unsigned int )pthread_self(), ( unsigned int )&errno, esp ); shared_func( ( unsigned int )-1 ); for ( i = 0; i < thread_num; i++ ) { pthread_join( tid[i], NULL ); } if ( NULL != tid ) { free( tid ); tid = NULL; } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- # gcc-4.7 -fPIC -shared -Wl,-soname,libfoo.so -Wl,-m,elf_i386 -Wall -pipe -O3 -g3 -o libfoo.so foo.c -pthread # gcc-4.7 -Wl,-rpath,'$ORIGIN' -Wall -pipe -O3 -g3 -o bar bar.c -pthread -L. -lfoo # readelf -d bar | grep RPATH 0x0000000f (RPATH) Library rpath: [$ORIGIN] # ldd bar linux-gate.so.1 => (0xb773a000) libfoo.so => /tmp/./libfoo.so (0xb7736000) libpthread.so.0 => /lib/i386-linux-gnu/i686/cmov/libpthread.so.0 (0xb7700000) libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb759c000) /lib/ld-linux.so.2 (0xb773b000) # rm -rf /tmp/libfoo.so.profile 必须先删除"$LD_PROFILE_OUTPUT/$LD_PROFILE.profile",如果该文件已存在,新的 剖析数据会叠加上去(不是替换、覆盖),造成最后的剖析结果失真,比如某函数被调 用次数翻倍。 # LD_PROFILE=libfoo.so LD_PROFILE_OUTPUT=/tmp /tmp/bar 0xB75C96C0 [-1] &errno = 0xB75C9688 esp = 0xBFC7A350 0xB75C96C0 [4294967295] &magic_buf = 0xB7765900 &magic_int = 0xB77658E0 &errno = 0xB75C9688 esp = 0xBFC7A310 0xB6DBDB70 [1] &magic_buf = 0xB7765900 &magic_int = 0xB77658E0 &errno = 0xB6DBDB38 esp = 0xB6DBD340 0xB65BCB70 [2] &magic_buf = 0xB7765900 &magic_int = 0xB77658E0 &errno = 0xB65BCB38 esp = 0xB65BC340 0xB75BEB70 [0] &magic_buf = 0xB7765900 &magic_int = 0xB77658E0 &errno = 0xB75BEB38 esp = 0xB75BE340 # ls -l /tmp/libfoo.so.profile -rw-r--r-- 1 root root 13824 Nov 20 08:39 /tmp/libfoo.so.profile # sprof -cpq /tmp/libfoo.so /tmp/libfoo.so.profile Inconsistency detected by ld.so: dl-open.c: 690: _dl_open: Assertion `_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT' failed! # 参看: Race in _dl_open with r_debug.r_state consistency check - Suzuki [2006-10-27] http://www.sourceware.org/bugzilla/show_bug.cgi?id=3429 上述sprof命令触发了一个GLIBC的BUG,按说2009年就补了,怎么现在又出来了? # scz-gdb-7.6.1 -q sprof Reading symbols from /usr/bin/sprof...(no debugging symbols found)...done. (gdb) tb __GI___assert_fail Function "__GI___assert_fail" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Temporary breakpoint 1 (__GI___assert_fail) pending. (gdb) r -cpq /tmp/libfoo.so /tmp/libfoo.so.profile Starting program: /usr/bin/sprof -cpq /tmp/libfoo.so /tmp/libfoo.so.profile Temporary breakpoint 1, *__GI___assert_fail (assertion=0xb7ffb6e8 "_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT", file=0xb7ffc81c "dl-open.c", line=690, function=0xb7ffc892 <__PRETTY_FUNCTION__.9891> "_dl_open") at dl-minimal.c:207 207 { (gdb) bt 2 #0 *__GI___assert_fail (assertion=0xb7ffb6e8 "_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT", file=0xb7ffc81c "dl-open.c", line=690, function=0xb7ffc892 <__PRETTY_FUNCTION__.9891> "_dl_open") at dl-minimal.c:207 #1 0xb7ff3b7f in _dl_open (file=0xbffffdd7 "/tmp/libfoo.so", mode=-1073741823, caller_dlopen=0x8049495, nsid=-2, argc=4, argv=0xbffffc94, env=0xbffffca8) at dl-open.c:690 (More stack frames follow...) (gdb) x/i 0xb7ff3b7f 0xb7ff3b7f <_dl_open+271>: mov eax,DWORD PTR [ebp-0x38] (gdb) disas _dl_open Dump of assembler code for function _dl_open: ... 0xb7ff3b1d <+173>: call 0xb7ff0970 <_dl_debug_initialize> 0xb7ff3b22 <+178>: mov eax,DWORD PTR [eax+0xc] 0xb7ff3b25 <+181>: test eax,eax 0xb7ff3b27 <+183>: jne 0xb7ff3b55 <_dl_open+229> 0xb7ff3b29 <+185>: mov DWORD PTR [esp],esi 0xb7ff3b2c <+188>: call DWORD PTR [ebx+0x824] ... 0xb7ff3b55 <+229>: lea eax,[ebx-0x2762] 0xb7ff3b5b <+235>: mov DWORD PTR [esp+0xc],eax 0xb7ff3b5f <+239>: mov DWORD PTR [esp+0x8],0x2b2 0xb7ff3b67 <+247>: lea eax,[ebx-0x27d8] 0xb7ff3b6d <+253>: mov DWORD PTR [esp+0x4],eax 0xb7ff3b71 <+257>: lea eax,[ebx-0x390c] 0xb7ff3b77 <+263>: mov DWORD PTR [esp],eax 0xb7ff3b7a <+266>: call 0xb7ff6aa0 <*__GI___assert_fail> 0xb7ff3b7f <+271>: mov eax,DWORD PTR [ebp-0x38] ... End of assembler dump. (gdb) p/x RT_CONSISTENT $1 = 0x0 (gdb) p/x RT_ADD $2 = 0x1 在_dl_open+181处eax不等于0(RT_CONSISTENT),跳走,调用__GI___assert_fail()。 在gdb中将该处的eax手工调成0,看到的运行结果如下: # scz-gdb-7.6.1 -q sprof Reading symbols from /usr/bin/sprof...(no debugging symbols found)...done. (gdb) tb _dl_open Function "_dl_open" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Temporary breakpoint 1 (_dl_open) pending. (gdb) r -cpq /tmp/libfoo.so /tmp/libfoo.so.profile Starting program: /usr/bin/sprof -cpq /tmp/libfoo.so /tmp/libfoo.so.profile Temporary breakpoint 1, _dl_open (file=0xbffffdd7 "/tmp/libfoo.so", mode=-1073741823, caller_dlopen=0x8049495, nsid=-2, argc=4, argv=0xbffffc94, env=0xbffffca8) at dl-open.c:573 573 holes. This is a pessimistic assumption which won't hurt (gdb) tb *_dl_open+181 Temporary breakpoint 2 at 0xb7ff3b25: file dl-open.c, line 690. (gdb) c Continuing. Temporary breakpoint 2, 0xb7ff3b25 in _dl_open (file=0xbffffdd7 "/tmp/libfoo.so", mode=-1073741823, caller_dlopen=0x8049495, nsid=-2, argc=4, argv=0xbffffc94, env=0xbffffca8) at dl-open.c:690 (gdb) i r eax eax 0x1 1 (gdb) set $eax=0 (gdb) c Continuing. Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls us/call us/call name 0.00 0.00 0.00 4 0.00 shared_func index % time self children called name 0.00 0.00 4/4 [0] 0.0 0.00 0.00 4 shared_func [0] ----------------------------------------------- shared_func 4 [Inferior 1 (process 9052) exited normally] (gdb) 剖析libfoo.so没得到什么有价值的信息,试试libc.so.6: # rm -rf /tmp/libc.so.6.profile # LD_PROFILE=libc.so.6 LD_PROFILE_OUTPUT=/tmp /tmp/bar # sprof -cpq libc.so.6 /tmp/libc.so.6.profile *** The file `libc.so.6' is stripped: no detailed analysis possible Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls us/call us/call name 0.00 0.00 0.00 14 0.00 free 0.00 0.00 0.00 6 0.00 getpagesize 0.00 0.00 0.00 5 0.00 printf 0.00 0.00 0.00 3 0.00 __libc_thread_freeres 0.00 0.00 0.00 3 0.00 _setjmp 0.00 0.00 0.00 3 0.00 clone 0.00 0.00 0.00 3 0.00 madvise 0.00 0.00 0.00 3 0.00 mmap 0.00 0.00 0.00 3 0.00 mprotect 0.00 0.00 0.00 2 0.00 __cxa_finalize 0.00 0.00 0.00 1 0.00 __libc_dl_error_tsd 0.00 0.00 0.00 1 0.00 __libc_pthread_init 0.00 0.00 0.00 1 0.00 __libc_start_main 0.00 0.00 0.00 1 0.00 calloc 0.00 0.00 0.00 1 0.00 getrlimit 0.00 0.00 0.00 1 0.00 sysconf 0.00 0.00 0.00 1 0.00 uname index % time self children called name 0.00 0.00 1/1 [1] 0.0 0.00 0.00 1 __libc_start_main [1] ----------------------------------------------- 0.00 0.00 3/3 [111] 0.0 0.00 0.00 3 _setjmp [111] ----------------------------------------------- 0.00 0.00 2/2 [172] 0.0 0.00 0.00 2 __cxa_finalize [172] ----------------------------------------------- [257] 0.0 0.00 0.00 0 vfprintf [257] 0.00 0.00 10/14 free [512] ----------------------------------------------- 0.00 0.00 5/5 [268] 0.0 0.00 0.00 5 printf [268] ----------------------------------------------- 0.00 0.00 1/1 [509] 0.0 0.00 0.00 1 calloc [509] ----------------------------------------------- 0.00 0.00 4/14 0.00 0.00 10/14 vfprintf [257] [512] 0.0 0.00 0.00 14 free [512] ----------------------------------------------- 0.00 0.00 1/1 [796] 0.0 0.00 0.00 1 uname [796] ----------------------------------------------- 0.00 0.00 1/1 [840] 0.0 0.00 0.00 1 sysconf [840] ----------------------------------------------- 0.00 0.00 1/1 [1026] 0.0 0.00 0.00 1 getrlimit [1026] ----------------------------------------------- 0.00 0.00 6/6 [1051] 0.0 0.00 0.00 6 getpagesize [1051] ----------------------------------------------- 0.00 0.00 3/3 [1119] 0.0 0.00 0.00 3 mmap [1119] ----------------------------------------------- 0.00 0.00 3/3 [1122] 0.0 0.00 0.00 3 mprotect [1122] ----------------------------------------------- 0.00 0.00 3/3 [1124] 0.0 0.00 0.00 3 madvise [1124] ----------------------------------------------- 0.00 0.00 3/3 [1197] 0.0 0.00 0.00 3 clone [1197] ----------------------------------------------- 0.00 0.00 1/1 [1400] 0.0 0.00 0.00 1 __libc_pthread_init [1400] ----------------------------------------------- 0.00 0.00 1/1 [1812] 0.0 0.00 0.00 1 __libc_dl_error_tsd [1812] ----------------------------------------------- 0.00 0.00 3/3 [1906] 0.0 0.00 0.00 3 __libc_thread_freeres [1906] ----------------------------------------------- __libc_start_main 1 _setjmp 3 __cxa_finalize 2 vfprintf free 10 printf 5 calloc 1 free 4 uname 1 sysconf 1 getrlimit 1 getpagesize 6 mmap 3 mprotect 3 madvise 3 clone 3 __libc_pthread_init 1 __libc_dl_error_tsd 1 __libc_thread_freeres 3 解释一下这段数据: ----------------------------------------------- 0.00 0.00 4/14 0.00 0.00 10/14 vfprintf [257] [512] 0.0 0.00 0.00 14 free [512] ----------------------------------------------- 意思是free()的编号是[512],总共被调用了14次,其中调了4次free(), 编号为[257]的vfprintf()调了10次free()。 没看明白sprof的输出有什么价值,对比一下: # ltrace -c /tmp/bar ... % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 62.34 0.003354 1118 3 pthread_join 15.37 0.000827 275 3 pthread_create 15.24 0.000820 820 1 printf 2.84 0.000153 153 1 shared_func 2.42 0.000130 130 1 calloc 0.76 0.000041 41 1 free 0.54 0.000029 29 1 __errno_location 0.48 0.000026 26 1 pthread_self ------ ----------- ----------- --------- -------------------- 100.00 0.005380 12 total 16.28 在Debian上手工升级GLIBC A: scz@nsfocus 2014-03-16 15:04 下面是Debian 7.2手工在线热升级GLIBC 2.19的最简步骤: -------------------------------------------------------------------------- 切换到普通用户(scz): unset LD_LIBRARY_PATH cd /home/scz/src wget http://ftp.gnu.org/gnu/libc/glibc-2.19.tar.xz xz -cd glibc-2.19.tar.xz | tar xf - cd /home/scz/src/glibc-2.19 sed -i 's/\\$$(pwd)/`pwd`/' timezone/Makefile mkdir -p /home/scz/src/glibc-build cd /home/scz/src/glibc-build CC=gcc-4.7 CFLAGS="-g -O2 -march=i686" ../glibc-2.19/configure --prefix=/usr --enable-add-ons --enable-kernel=2.6.32 --disable-profile i686-pc-linux-gnu LC_ALL=C make -j 6 LC_ALL=C make dvi -------------------------------------------------------------------------- 切换到root: export LD_LIBRARY_PATH=/lib cd /home/scz/src/glibc-build cp -a /usr/include /usr/include-old env LANGUAGE=C LC_ALL=C make install mv /usr/include /usr/include-new cp -a /usr/include-old /usr/include ldconfig -p | grep libc.so.6 libc.so.6 (libc6,x86-64, OS ABI: Linux 2.6.26) => /lib64/libc.so.6 libc.so.6 (libc6, OS ABI: Linux 2.6.32) => /lib/libc.so.6 libc.so.6 (libc6, OS ABI: Linux 2.6.26) => /lib/i386-linux-gnu/libc.so.6 ldd --version -------------------------------------------------------------------------- 建议升级完GLIBC务必不要马上reboot,立刻新登录一个root shell,做如下检查: cd /tmp echo $LD_LIBRARY_PATH ldconfig -p | grep libc.so.6 LD_DEBUG=libs /bin/ls --help 2>&1 | grep "calling init" ldd /sbin/init 确认没有出现混搭现象。如果init出现混搭,reboot就是灾难。 -------------------------------------------------------------------------- A: scz@nsfocus 2014-03-19 14:43 Debian自己用的是EGLIBC,不是标准的GLIBC。 -------------------------------------------------------------------------- 切换到root: apt-get build-dep eglibc aptitude install devscripts aptitude install sbuild 切换到scz: unset LD_LIBRARY_PATH cd /home/scz/src rm -rf eglibc-2.13 apt-get source libc6:i386 dpkg-source -x eglibc_2.13-38+deb7u1.dsc cd eglibc-2.13/ QUILT_PATCHES=debian/patches quilt push -a make -f debian/rules patch vi debian/rules 注释掉: #standard-add-ons = libidn, debuild -i -us -uc -b -ai386 -ti386-linux-gnu -j6 cd .. ls *.deb eglibc-source_2.13-38+deb7u1_all.deb libc6-dev-amd64_2.13-38+deb7u1_i386.deb libc-dev-bin_2.13-38+deb7u1_i386.deb glibc-doc_2.13-38+deb7u1_all.deb libc6-i686_2.13-38+deb7u1_i386.deb locales_2.13-38+deb7u1_all.deb libc6_2.13-38+deb7u1_i386.deb libc6-pic_2.13-38+deb7u1_i386.deb locales-all_2.13-38+deb7u1_i386.deb libc6-amd64_2.13-38+deb7u1_i386.deb libc6-prof_2.13-38+deb7u1_i386.deb multiarch-support_2.13-38+deb7u1_i386.deb libc6-dbg_2.13-38+deb7u1_i386.deb libc6-xen_2.13-38+deb7u1_i386.deb nscd_2.13-38+deb7u1_i386.deb libc6-dev_2.13-38+deb7u1_i386.deb libc-bin_2.13-38+deb7u1_i386.deb ls *.udeb libc6-udeb_2.13-38+deb7u1_i386.udeb libnss-dns-udeb_2.13-38+deb7u1_i386.udeb libnss-files-udeb_2.13-38+deb7u1_i386.udeb -------------------------------------------------------------------------- 切换到root: unset LD_LIBRARY_PATH cd /home/scz/src dpkg -i libc-bin_2.13-38+deb7u1_i386.deb dpkg -i libc6_2.13-38+deb7u1_i386.deb dpkg -i libc6-dbg_2.13-38+deb7u1_i386.deb dpkg -i libc-dev-bin_2.13-38+deb7u1_i386.deb dpkg -i libc6-dev_2.13-38+deb7u1_i386.deb dpkg -i multiarch-support_2.13-38+deb7u1_i386.deb dpkg -i libc6-i686_2.13-38+deb7u1_i386.deb ls -l /lib/i386-linux-gnu/i686/cmov/libc-2.13.so 查看更新时间,注意到cmov子目录被更新过,这是GLIBC 2.19所没有的效果,估计是 EGLIBC的效果。 # ldd --version ldd (Debian EGLIBC 2.13-38+deb7u1) 2.13 -------------------------------------------------------------------------- D: scz@nsfocus 2014-03-21 09:39 vi /etc/apt/sources.list -------------------------------------------------------------------------- deb http://ftp.cn.debian.org/debian stable main non-free contrib deb-src http://ftp.cn.debian.org/debian/ stable main non-free contrib deb http://ftp.cn.debian.org/debian testing main non-free contrib deb-src http://ftp.cn.debian.org/debian/ testing main non-free contrib -------------------------------------------------------------------------- vi /etc/apt/apt.conf -------------------------------------------------------------------------- APT::Cache-Limit "40000000"; -------------------------------------------------------------------------- apt-get update -u aptitude install gcc-4.8 本意是只装gcc-4.8的,结果它的依赖关系吓死人,要求升到eglibc 2.18-4。我升级 gcc-4.8的目的是自己编译eglibc 2.18-4,现在好嘛,直接二进制升级eglibc 2.18-4。 但这次的依赖升级只升了libc6:i386,没有升libc-bin,得手工补: aptitude install libc-bin 再试一次自己编译eglibc 2.18-4: -------------------------------------------------------------------------- 切换到root: apt-get build-dep eglibc aptitude install devscripts aptitude install sbuild aptitude install libselinux1-dev aptitude install rdfind aptitude install symlinks 切换到scz: unset LD_LIBRARY_PATH cd /home/scz/src wget http://ftp.cn.debian.org/debian/pool/main/e/eglibc/eglibc_2.18.orig.tar.xz wget http://ftp.cn.debian.org/debian/pool/main/e/eglibc/eglibc_2.18-4.dsc wget http://ftp.cn.debian.org/debian/pool/main/e/eglibc/eglibc_2.18-4.debian.tar.xz dpkg-source -x eglibc_2.18-4.dsc cd eglibc-2.18/ QUILT_PATCHES=debian/patches quilt push -a make -f debian/rules patch debuild -i -us -uc -b -ai386 -ti486-linux-gnu -j6 cd .. ls *.deb eglibc-source_2.18-4_all.deb libc6-dev_2.18-4_i386.deb libc6-prof_2.18-4_i386.deb locales_2.18-4_all.deb glibc-doc_2.18-4_all.deb libc6-dev-amd64_2.18-4_i386.deb libc6-x32_2.18-4_i386.deb locales-all_2.18-4_i386.deb libc6_2.18-4_i386.deb libc6-dev-x32_2.18-4_i386.deb libc6-xen_2.18-4_i386.deb multiarch-support_2.18-4_i386.deb libc6-amd64_2.18-4_i386.deb libc6-i686_2.18-4_i386.deb libc-bin_2.18-4_i386.deb nscd_2.18-4_i386.deb libc6-dbg_2.18-4_i386.deb libc6-pic_2.18-4_i386.deb libc-dev-bin_2.18-4_i386.deb ls *.udeb libc6-udeb_2.18-4_i386.udeb libnss-dns-udeb_2.18-4_i386.udeb libnss-files-udeb_2.18-4_i386.udeb -------------------------------------------------------------------------- 切换到root: unset LD_LIBRARY_PATH cd /home/scz/src dpkg -i libc6_2.18-4_i386.deb dpkg -i libc6-i686_2.18-4_i386.deb dpkg -i libc6-dbg_2.18-4_i386.deb dpkg -i libc-bin_2.18-4_i386.deb dpkg -i libc6-dev_2.18-4_i386.deb dpkg -i libc-dev-bin_2.18-4_i386.deb dpkg -i multiarch-support_2.18-4_i386.deb ls -l /lib/i386-linux-gnu/i686/cmov/libc-2.18.so # ldd --version ldd (Debian EGLIBC 2.18-4) 2.18 -------------------------------------------------------------------------- 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@nsfocus 下面几种方法都可以 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。 A: scz@nsfocus 2013-11-19 13:45 man -P $(which cat) 1 sed > sed.txt 17.5 在vi中如何忽略大小写搜索 A: :set ignorecase 或者 /something\c 17.6 如何用vi/xxd进行16进制编辑 A: vi :%!xxd -g 1 // 进入16进制编辑状态 :%!xxd -r // 退出16进制编辑状态,:wq退出前一定要先退出16进制编辑状态 17.7 vim and ctags Q: RSAS包含大量源码和函数定义,而且划分成多级目录,如何在任意目录位置快速查看 任意函数? A: lgx 使用vim+ctags,以RSAS源码根目录rsas为例: 1) 在rsas源码目录下执行"ctags -R"命令。 2) 假设rsas源码目录直接在用户目录下,修改~/.vimrc,增加一行: set tags=./tags,tags,~/rsas/tags 3) 在任意目录下执行"vim -t main_loop"都将直接跳到main_loop()函数。 18. 补丁相关问题 18.0 如何在Sun主站上查询补丁信息 Q: 假设我想查询111085-02的详细信息。 A: scz@nsfocus 2008-07-14 13:23 这有一个"Patch Finder": http://sunsolve.sun.com/show.do?target=patches/patch-access 输入111085-02即可。如果输入111085,返回的是最新小版本。 可以免费注册帐号 18.1 如何根据补丁号从Sun主站下载补丁 Q: 已经知道补丁号,可我如何从Sun主站下载这个补丁呢。有些补丁不是安全补丁,Sun 公司并未在主站上提供非安全补丁的下载链接。 A: scz@nsfocus 首先去如下链接申请一个帐号(免费): http://sunsolve.sun.com 其次假设补丁号是107589,参看如下链接: http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=patches/107589 wget -O 107589.tar.Z http://username:password@sunsolve.sun.com/private-cgi/patchDownload.pl?target=107589&method=h 然后用"file 107589.tar.Z"命令确认这个文件格式,选择不同的解压工具。补丁版 本号在压缩包内有体现。这个方法适用于所有补丁,包括非安全补丁。这样下载获得 的补丁是最新版。 如果你不但知道补丁号,还知道补丁版本号,也可以这样下载 wget http://username:password@sunsolve.sun.com/private-cgi/patches/107589-06.zip 一般只提供最新版补丁,旧版补丁将被删除。 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 Infodoc ID - 16110 http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=infodoc/16110 使用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@nsfocus 2004-03-16 16:45 一般安装完补丁后,查看如下目录与文件: /var/sadm/patch// (有一些说明、安装日志、卸载脚本) /var/sadm/pkg//save//undo.Z (卸载时所用备份文件) 18.3 patchdiag如何使用 Q: 我想知道系统当前补丁状态 A: Sun Microsystems 2000-08-21 Infodoc ID - 17915 http://sunsolve.sun.com/private-cgi/retrieve.pl?type=0&doc=infodoc/17915 1) 访问如下链接: http://sunsolve.Sun.COM/private-cgi/show.pl?target=resources/patchdiag 下载: http://sunsolve.sun.com/diag/patchdiag/patchdiag_1.0.4.tar.Z 2) zcat patchdiag_1.0.4.tar.Z | tar xvf - 3) patchdiag-1.0.4/目录下应该有如下文件 COPYRIGHT README patchdiag.1m (man手册) patchdiag.i386* patchdiag.pl patchdiag.sparc* patchdiag.xref (有关补丁信息的交叉引用数据文件,单独下载) patchdiag_setup* patchdiag_userguide 4) 下载: http://sunsolve.sun.com/private-cgi/patches/patchdiag.xref 这个文件包含了有关补丁信息的最新数据。 5) 运行patchdiag_setup脚本,提示输入patchdiag.xref文件所在目录名,最终生成 patchdiag脚本。 6) 在运行patchdiag脚本前,阅读patchdiag_userguide文件。 下面内容实际是 <> 该工具可运行于Solaris 2.3及其以后版本上,但不适合于Sun OS 4.x。为了运行这 个工具,系统中必须有如下文件: patchdiag.xref 这是一个有关补丁信息的交叉引用数据文件,包含了所有补丁信息。可以从这个 链接获取,http://sunsolve.sun.com/private-cgi/patches/patchdiag.xref /usr/bin/pkginfo patchdiag使用pkginfo命令获取当前系统中安装包信息,该命令从Solaris 2.3 开始提供 /usr/bin/showrev patchdiag使用showrev命令获取当前系统中补丁信息,该命令从Solaris 2.3开 始提供 /usr/bin/uname 如果没有指定-s或者-p选项,patchdiag使用uname命令确定系统信息 patchdiag [ -l ] [ -s ] [ -p ] [ -x ] [ -h | -? ] sparc 或者 i386,如果没有指定,假设是sparc 包含"showrev -p"输出信息 包含"pkginfo -l"输出信息 操作系统版本号,必须是SunOS版本号,而不能是Solaris版本号,比如为 了描述"Solaris 2.5",应该用"5.5",而不是"2.5" 有关补丁信息的交叉引用数据文件,缺省文件名patchdiag.xref 如果什么都未指定,patchdiag激活"showrev -p"命令,显示标准审计报告,指明哪 些补丁已安装,哪些推荐补丁未安装,哪些安全补丁未安装。 -l 显示更详细的审计报告,包含已安装补丁的其他相关补丁信息。 7) 运行patchdiag脚本 # ./patchdiag -l > patchdiag.out # vi -R patchdiag.out (举例如下) ====================================================================================== System Name: ... SunOS Vers: 5.6 Arch: sparc Cross Reference File Date: Jun/15/01 PatchDiag Version: 1.0.4 ====================================================================================== ... ... ====================================================================================== INSTALLED PATCHES Patch Installed Latest Synopsis ID Revision Revision ------ --------- -------- ------------------------------------------------------------ 105160 02 14 CDE 1.2: dtterm libDtTerm.so.1 patch 105181 05 26 SunOS 5.6: Kernel update patch 105189 02 03 OBSOLETED by 106040 105210 05 38 SunOS 5.6: libaio, libc & watchmalloc patch 105214 01 CURRENT OBSOLETED by 105181 ... ... 107565 02 CURRENT SunOS 5.6: /usr/sbin/in.tftpd patch ====================================================================================== UNINSTALLED RECOMMENDED PATCHES Patch Ins Lat Age Require Incomp Synopsis ID Rev Rev ID ID ------ --- --- --- --------- --------- ----------------------------------------- 105395 N/A 06 734 SunOS 5.6: /usr/lib/sendmail patch ... ... 106437 N/A 03 503 105669-06 CDE 1.2: Print Manager Patch ... ... 106650 N/A 04 545 106648-01 OpenWindows 3.6: mailtool attachment security patch 106649-01 ... ... ====================================================================================== UNINSTALLED SECURITY PATCHES NOTE: This list includes the Security patches that are also Recommended Patch Ins Lat Age Require Incomp Synopsis ID Rev Rev ID ID ------ --- --- --- --------- --------- ----------------------------------------- 105395 N/A 06 734 SunOS 5.6: /usr/lib/sendmail patch ... ... 106437 N/A 03 503 105669-06 CDE 1.2: Print Manager Patch ... ... 111240 N/A 01 38 SunOS 5.6: Patch to /usr/bin/finger ====================================================================================== UNINSTALLED Y2K PATCHES NOTE: This list includes the Y2K patches that are also Recommended Patch Ins Lat Age Require Incomp Synopsis ID Rev Rev ID ID ------ --- --- --- --------- --------- ----------------------------------------- 106828 N/A 01 963 SunOS 5.6: /usr/bin/date patch 107492 N/A 01 805 SunOS 5.6: Y2000, runacct cannot update /var/adm/acct/sum/loginlog 107988 N/A 01 598 SunOS 5.6: Patch for SPARCompiler Binary Compatibility Libraries 108667 N/A 03 495 CDE 1.2: perfmeter is not Y2K compliant in SunOS 5.6 Supplement 108671 N/A 03 342 OpenWindows 3.6: Calendar Manager patch ====================================================================================== OTHER RELATED UNINSTALLED PATCHES NOTE: This is determined by the packages that have been installed on the system. When one patch refers to multiple packages, we list the additional packages in the next lines. The various 'S','R','*' marks denote unbundled packages that is designated as an 'Security' or 'Recommended'. S = Security R = Recommened Unbundled * = Both Security and Recommended Unbundled Patch Package Lat Age Synopsis ID Name Rev ------ - --------- --- --- ------------------------------------------------------------ ... ... 104829 S SUNWlicsw 02 542 FLEXlm 4.1c: Patch for FLEXlm 4.1c and lit/lit_tty SUNWlit 104844 SPROesrt 06 917 WorkShop IPE 4.0: Patch SPROmktl SPROws ... ... 105178 SPROprfan 01 1404 WorkShop IPE 4.0: Analyzer patch for Solaris 2.x 105284 R SUNWmfrun 41 18 Motif 1.2.7: Runtime library patch ... ... 111435 SUNWcar 01 45 SunOS 5.6: Supplemental kernel update patch SUNWcsr ====================================================================================== 上述输出信息非常清楚地指明了系统当前补丁状态。 18.4 给Solaris 2.6安装推荐补丁集(未完成) Q: 我想给SPARC/Solaris 2.6安装推荐补丁集,该如何着手进行 D: scz@nsfocus 访问: http://sunsolve.sun.com/pub-cgi/show.pl?target=patches/patch-access wget http://username:password@sunsolve.sun.com/private-cgi/patches/2.6_Recommended.tar.Z 2.6_x86_Recommended.tar.Z 7_Recommended.zip 7_x86_Recommended.zip 8_Recommended.zip 8_x86_Recommended.zip # cp 2.6_Recommended.tar.Z /tmp # cd /tmp # zcat 2.6_Recommended.tar.Z | tar xvf - 18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁 A: scz@nsfocus # 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@nsfocus mkdir -p /tmp/patch cd /tmp/patch wget -O 105181.tar.Z http://username:password@sunsolve.sun.com/private-cgi/patchDownload.pl?target=105181&method=h wget -O 106429.tar.Z http://username:password@sunsolve.sun.com/private-cgi/patchDownload.pl?target=106429&method=h 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@nsfocus 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: scz@nsfocus 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 Infodoc ID - 11270 http://sunsolve.sun.com/private-cgi/retrieve.pl?doc=infodoc/11270 执行"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: scz@nsfocus 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@nsfocus 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: scz@nsfocus 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; char buf[80]; if ( 0 == isatty( STDIN_FILENO ) ) { 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 ( NULL != fgets( buf, sizeof( buf ), stdin ) ); if ( 0 != feof( stdin ) ) { 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: scz@nsfocus 1998 有一个非常邪门同时非常有效的办法,执行cat `which cal`即可。 A: rlc@SMTH 2006-02-28 13:24:48 在命令行上输入Ctrl-V/N(按住Ctrl不松,依次按V和N),回车后终端显示就会乱掉。 此时有几种办法可供恢复。 $ cat(回车) Ctrl-V/O(回车) Ctrl-D (有时可能需要Ctrl-C) $ 或者: $ `which echo` "\017" 注意这里用的是外置echo命令,不是内置echo命令,因为外置echo命令更具可移植性。 D: http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters ^N(0x0E)是控制字符Shift Out(SO),^O(0x0F)是控制字符Shift In(SI)。它们一般 用来在不同字符集之间进行切换。比如,在俄文KOI-7字符集中,SO表示后续字符使 用俄文字符,SI表示后续字符恢复使用拉丁字符。 19.8 minicom如何用 A: 1) 用root身份运行"minicom -s xxx"进行初始化设置。 2) 在"Serial port setup"中设置串口。 3) 在"Modem and dialing"中设置"Init string"、"Reset string",一般清空即可。 3) Save setup as xxx 4) Exit from Minicom 5) minicom xxx 先按一次Ctrl-A,全松开后再单独按Z,进入帮助界面。"Ctrl-A X"退出minicom。 19.9 为何Ctrl-C未能产生SIGINT(2) Q: plank@SMTH 2009-04-22 02:02 一个前台进程,从stdin输入,但这个进程不响应Ctrl-C。查看了termios结构的c_cc [VINTR],等于3,正常。程序里没有安装SIGINT信号处理函数。如果从另一个终端上 "kill -2 ",可以杀掉该进程。这说明之前按Ctrl-C并未将SIGINT投递到该进 程。为什么? A: scz@nsfocus 2009-04-23 11:16 至少有一种可能,c_lflag的ISIG被复位了,此时Ctrl-C不会产生SIGINT信号。下面 这个Disable_Ctrl_C.c演示了这种效果。 -------------------------------------------------------------------------- /* * For x86/Linux Kernel 2.6.18-4 * gcc-3.3 -Wall -pipe -O3 -o Disable_Ctrl_C Disable_Ctrl_C.c */ #include #include #include #include #include static struct termios original_term; /* * for signal handlers */ typedef void Sigfunc ( int ); static void Atexit ( void ( *func ) ( void ) ) { if ( 0 != atexit( func ) ) { /* * perror( "atexit error" ); */ exit( EXIT_FAILURE ); } return; } /* end of Atexit */ static void on_sigint ( int signo ) { /* * 演示用,不推荐在信号处理函数中使用fprintf() */ fprintf( stderr, "\nsigno = %d\n", signo ); /* * 这次我们使用atexit()函数 */ exit( EXIT_SUCCESS ); } /* end of on_sigint */ static Sigfunc * PrivateSignal ( 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 /* * 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( SIG_ERR ); } return( oact.sa_handler ); } /* end of PrivateSignal */ static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc; if ( SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) ) { perror( "signal" ); exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */ static void terminate ( void ) { /* * 恢复原始终端属性 */ tcsetattr( STDIN_FILENO, TCSANOW, &original_term ); _exit( EXIT_SUCCESS ); } /* end of terminate */ int main ( int argc, char * argv[] ) { int ret = EXIT_FAILURE; struct termios current_term; if ( isatty( STDIN_FILENO ) == 0 ) { fprintf( stderr, "standard input is not a terminal device\n" ); goto main_exit; } /* * 保存原始终端属性 */ if ( tcgetattr( STDIN_FILENO, &original_term ) < 0 ) { perror( "tcgetattr( STDIN_FILENO, ... ) failed" ); goto main_exit; } /* * 安装一些回调函数 */ Atexit( terminate ); Signal( SIGINT, on_sigint ); current_term = original_term; /* * When any of the characters INTR, QUIT, SUSP, or DSUSP are received, * generate the corresponding signal. */ current_term.c_lflag &= ~ISIG; /* * 也可以直接调用cfmakeraw(),这包括将ISIG复位的操作。 * * cfmakeraw( ¤t_term ); */ if ( tcsetattr( STDIN_FILENO, TCSANOW, ¤t_term ) < 0 ) { perror( "tcsetattr( STDIN_FILENO, TCSANOW, ... ) failed" ); goto main_exit; } /* * 产生阻塞 */ getchar(); ret = EXIT_SUCCESS; printf( "ok\n" ); main_exit: return( ret ); } /* end of main */ -------------------------------------------------------------------------- $ ./Disable_Ctrl_C ^C^C^C // getchar()产生阻塞,在此狂按Ctrl-C,没反应 signo = 2 // 从另一个终端上"kill -2 ",on_sigint()被执行 $ 从另一个终端上执行如下命令投递SIGINT信号: # kill -INT `ps auwx | grep Disable_Ctrl_C | grep -v grep | awk '{print $2;}'` # kill -INT `pidof -s Disable_Ctrl_C` 参看termios(3)手册页了解更多信息。 20. shell script问题 20.0 不用临时文件完成字符串替换 Q: 将/tmp/target.txt中的/var/NSFocus/tmp替换成/var/tmp,要求在shell script中 完成,不使用临时文件。 A: scz@nsfocus -------------------------------------------------------------------------- #! /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@nsfocus 参看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: scz@nsfocus 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: yjl 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: scz@nsfocus 2001-12-17 10:23 n<&- 关闭输入文件句柄n n>&- 关闭输出文件句柄n n>&m 复制句柄n成为输出句柄m的备份 n<&m 复制句柄n成为输入句柄m的备份 nfile 写打开file获取一个文件句柄n 下面这个脚本在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: scz@nsfocus 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@nsfocus 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: scz@nsfocus ${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: scz@nsfocus 在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: scz@nsfocus 先来看一个显示目录树的经典shell script(/usr/local/bin/tree): -------------------------------------------------------------------------- #! /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@nsfocus # 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: scz@nsfocus 如果要处理整个目录树的话,可以这样 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@nsfocus -------------------------------------------------------------------------- #! /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@nsfocus 下述办法具有极大可移植性: 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@nsfocus 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@nsfocus 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 '(? ' alias ls='ls -aF' #export TERM=vt100 -------------------------------------------------------------------------- 注意不要将终端类型设成vt100,否则"HT Editor"这类动用了(n)curses的工具不能 正常使用。终端类型是xterm时"HT Editor"可以正常使用。 20.21 非交互模式修改用户口令 A: baoz 2008-05-05 echo "" | passwd --stdin 但Debian自带的passwd可能不支持--stdin参数,此时可以用: echo ":" | chpasswd # dpkg -S `which chpasswd` passwd: /usr/sbin/chpasswd 20.22 Debian自带的grep不支持-P Q: 在Debian上执行"grep --help"可以看到: -P, --perl-regexp PATTERN is a Perl regular expression 但实际测试表明Debian自带的grep不支持-P: $ 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.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@nsfocus 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命令增加 路由。 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@nsfocus 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 本文来自<>,参看如下链接 http://freebsd.sinica.edu.tw/~statue/zh-tut/ locale 指定一组C语言处理自然语言(文字)的方式,也可以简单地说,locale反映了 一组"地区性语言"的配置信息 LC_ALL 代表所有的locale(如下) LC_CTYPE 字符定义(包含字符分类与转换规则) LC_MESSAGES 信息显示 LC_TIME 时间格式 LC_NUMERIC 数字格式 LC_MONETARY 货币格式 LC_COLLATE 字母顺序与特殊字符比较顺序 其中与一般使用者息息相关的是是LC_CTYPE与LC_MESSAGES。LC_CTYPE直接关系到某 些字符或內码在目前locale下是否可显示,要如何转换编码,对应到哪一个字等等。 LC_MESSAGES则关系到软件的信息输出是否符合地域性,例如我们需要的是中文。而 一个真正完整支持locale的系统,是当使用者在shell prompt下,直接设置好环境变 量后就马上切换到那种语言了,例如: % export LC_CTYPE=zh_TW.Big5 设置locale的字符定义为台湾地区的Big5繁体中文码定义。有了正确的locale定义后, 使得任何地区的的文字,只要在加入适当的locale data之后,C Library就能正确地 处理软件显示信息,而我们使用的[中文]当然也不例外。 21.10 用cvsup安装vim A: deepin & scz@nsfocus 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@nsfocus 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: scz@nsfocus 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.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: scz@nsfocus 2002-01-09 13:46 1) 启动时加载/kernel前的10s暂停如何调整 autoboot_delay="10" # Delay in seconds before autobooting 21.19 FreeBSD中sysctl可控内核参数 D: scz@nsfocus 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: scz@nsfocus 应该也可以用mmap()通过/dev/mem映射0xB8000吧,参看"直接访问内存[显存]地址" 21.22 FreeBSD下如何为指定用户设定chroot的FTP环境 A: backend@nsfocus 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@nsfocus 用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: scz@nsfocus 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 21.33 FreeBSD如何手工配置TCP/IP协议栈 A: 以FreeBSD 4.5-RELEASE为例: # cat /etc/rc.conf # Enable network daemons for user convenience. # Please make all changes to this file, not to /etc/defaults/rc.conf. # This file now contains just the overrides from /etc/defaults/rc.conf. defaultrouter="10.10.5.254" hostname="FreeBSD_4_5" ifconfig_lnc0="inet 10.10.5.26 netmask 255.255.255.0" kern_securelevel_enable="NO" nfs_reserved_port_only="YES" sendmail_enable="NO" sshd_enable="NO" usbd_enable="YES" portmap_enable="NO" 执行/stand/sysinstall进入curses配置界面,实际操作的就是上述文件。 /stand/sysinstall->Configure->Networking->Interfaces->lnc0 如果想热生效,可以这样做: /sbin/ifconfig lnc0 10.10.5.26 netmask 255.255.255.0 broadcast 10.10.5.255 up /sbin/route -n add default 10.10.5.254 /usr/sbin/arp -da 还可以这样添加缺省路由: /sbin/route -n add 0/0 10.10.5.254 /sbin/route -n add -net 0.0.0.0 -netmask 0.0.0.0 -gateway 10.10.5.254 删除缺省路由: /sbin/route -n delete default 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如何手工配置TCP/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 cat /etc/issue cat /etc/issue.net cat /etc/slackware-version cat /etc/motd cat /etc/debian_version cat /proc/version 如果"uname -a"可以看出是何种Unix,就不必这样操作。 23.5 VI系列 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 Q: vi时前面有行号,我不想看到行号。 A: set nonu 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 修改成上述设置后,一切恢复正常。 23.10 Debian 4.0 KDE不允许root登录 A: wyvern2004 2005-02-14 10:37 gnome /etc/gdm/gdm.conf AllowRoot=true kde ps auwx | grep kdm /etc/kde3/kdm/kdmrc /etc/kde4/kdm/kdmrc /var/run/kdm/kdmrc AllowRootLogin=true D: X下获取root权限 http://www.debian.org/doc/manuals/reference/ch-tune.zh-cn.html#s-ss-xsu D: Ctrl-Alt-F1(n)可以切换到tty1(n),可以用root登录,停止X(比如kill掉kdm进程) 之后,试试startx。 Ctrl-Alt-F7(8)可以切换回KDE。 23.12 Debian上如何安装配置telnet服务 A: apt-get update -u apt-cache search telnetd apt-get install telnetd 一般安装完就自动启动了: netstat -na -t | grep :23 缺省不允许root远程telnet登录,此时有两种解决办法: 1) vi /etc/securetty 指定允许root远程telnet访问的伪终端: pts/0 pts/1 pts/2 pts/3 pts/4 pts/5 2) vi /etc/pam.d/login 注释掉如下内容,一劳永逸地解决问题: # Disallows root logins except on tty's listed in /etc/securetty # (Replaces the `CONSOLE' setting from login.defs) #auth requisite pam_securetty.so 从安全角度不建议如此做。 23.13 Debian上如何更改locale A: 用SecureCRT远程登录Linux使用(n)curses界面时考虑用如下设置: -------------------------------------------------------------------------- Session Options Emulation Terminal Linux ANSI Color Appearance Font vt100 10pt Character Default Cursor Vertical Bar -------------------------------------------------------------------------- # export LANG=C # dpkg-reconfigure locales 这是一个curses界面的配置工具,安装相应的locals,指定默认的locals。 # export LANG=zh_CN.UTF-8 # locale LANG=zh_CN.UTF-8 LANGUAGE=zh_CN:zh LC_CTYPE="zh_CN.UTF-8" LC_NUMERIC="zh_CN.UTF-8" LC_TIME="zh_CN.UTF-8" LC_COLLATE="zh_CN.UTF-8" LC_MONETARY="zh_CN.UTF-8" LC_MESSAGES="zh_CN.UTF-8" LC_PAPER="zh_CN.UTF-8" LC_NAME="zh_CN.UTF-8" LC_ADDRESS="zh_CN.UTF-8" LC_TELEPHONE="zh_CN.UTF-8" LC_MEASUREMENT="zh_CN.UTF-8" LC_IDENTIFICATION="zh_CN.UTF-8" LC_ALL= 这是热生效的办法。 23.14 一个账号有两个口令可供登录 Q: 没有用/usr/bin/passwd,而是直接打开/etc/shadow修改了test账号的口令,发现新 旧口令都能用来登录。 排查中还发现以root身份执行passwd居然提示权限否定。 D: 暂时没有排查出结果,有一些备忘记录: /etc/pam.d/* /etc/nsswitch.conf yppasswd ypchsh yptest ypwhich -x ypcat passwd.byname passwd -r files lsattr /usr/bin/passwd chattr strace passwd test 2>&1 | more # ls -ld /etc/pam.d/ drwxr-xr-x 2 root root 4096 Aug 3 08:41 /etc/pam.d// # ls -l /usr/bin/passwd -rwsr-xr-x 1 root root 31640 Nov 23 2008 /usr/bin/passwd* # ls -l /etc/passwd -rw-r--r-- 1 root root 1324 Aug 17 2009 /etc/passwd # ls -l /etc/shadow -rw-r----- 1 root shadow 914 Aug 17 2009 /etc/shadow 23.15 SecureCRT远程登录时如何显示汉字 A: 试举一列,充分非必要配置如下: -------------------------------------------------------------------------- Session Options Emulation Terminal VT100 Appearance Font 新宋体 常规 10 字符集 中文 GB2312 Character UTF-8 -------------------------------------------------------------------------- # export LANG=en_US.UTF-8 23.16 telnet登录Debian时出现"Login timed out after 60 seconds" Q: telnet登录Debian,输完用户名正在输口令的过程中,出现"Login timed out after 60 seconds",始终无法完成口令的输入。 D: # vi /etc/ldap/ldap.conf 增加如下内容试试: -------------------------------------------------------------------------- bind_policy soft -------------------------------------------------------------------------- D: 重启syslogd试试。 D: 升级试试: apt-get update -u apt-get upgrade -u 23.17 打开bash的增强TAB扩展功能 A: tk@nsfocus 一般在bash下按TAB键,会有一些自动扩展功能,事实上另有一种增强TAB扩展功能: vi /etc/profile 在尾部增加如下内容(来自/etc/bash.bashrc): -------------------------------------------------------------------------- # enable bash completion in interactive shells if [ -f /etc/bash_completion ]; then . /etc/bash_completion fi -------------------------------------------------------------------------- 23.18 所有用户均无法以SSH登录 D: 可能的原因较多,下述操作仅供参考: # vi /etc/ssh/sshd_config -------------------------------------------------------------------------- # Change to no to disable tunnelled clear text passwords PasswordAuthentication yes -------------------------------------------------------------------------- 检查/var/log/auth.log,发现其中有如下错误信息: PAM unable to resolve symbol: pam_sm_acct_mgmt 确认PAM相关文件到位: # ls -l /lib/security/ # ls -l /etc/pam.d 重新安装相关软件包: # apt-get --reinstall install libpam-modules 有可能在/usr/sbin/base-config时没有启用MD5口令,而sshd默认使用MD5口令,做 如下修改解决问题: # vi /etc/pam.d/common-password -------------------------------------------------------------------------- #password required pam_unix.so nullok obscure min=4 max=8 md5 password required pam_unix.so nullok obscure -------------------------------------------------------------------------- 重启sshd: /etc/init.d/sshd restart 23.19 Debian上如何安装配置tftp服务 A: scz@nsfocus apt-get update -u apt-cache search tftpd apt-get install tftpd 一般安装完就自动启动了: netstat -na -u | grep :69 # mkdir /tftpboot # chmod 777 /tftpboot # ls -ld /tftpboot drwxrwxrwx 2 root root 4096 2008-07-02 18:15 /tftpboot/ 编辑/etc/inetd.conf,给/usr/sbin/in.tftpd指定参数: -s /tftpboot kill -1 使之生效。 如果tftpd提供下载服务,应该事先在服务端"chmod 444 downloadfile"。 如果tftpd提供上载服务,应该事先在服务端"chmod 222 uploadfile"。 这里仅指定了最小所需权限,放宽权限当然没问题。 A: Debian 4.0上TFTPD(8)手册页 -------------------------------------------------------------------------- 摘要 tftpd [-n] [-s] [] 描述 tftpd侦听69/UDP口,参看/etc/services。该服务一般通过inetd(8)启动,参看 /etc/inetd.conf。 tftp不要求帐号、口令即可访问远程系统。由于缺乏身份认证信息,in.tftpd在 处理get请求时只允许访问全局可读文件。而在处理put请求时,要求server端文 件名已存在且全局可写。 in.tftpd以nobody身份运行。 tftp只能访问中指定目录下的文件。假设该目录列表 为"/root/src/demo /tmp",则get请求可以访问/root/src/demo/somefile或者 /tmp/somefile。如果没有指定,缺省值为/tftpboot, 不过/etc/inetd.conf中一般指定成/srv/tftp。如果指定成/,意味着整个文件 系统可访问。 修改/etc/inetd.conf指定自己的目录列表,并kill -1 使之生效。 -n 以相对路径请求不存在的文件时,tftpd不产生"否定确认信息"。不过我测 了一下,没啥效果,搞不懂。 -s 相当于启动了chroot功能,所有绝对路径都将局限在chroot环境中。chroot 的根路径为中的第一个目录,如果没有指定 ,则使用/tftpboot。如果指定了-s /tmp,而get 请求/somedir/somefile,最终实际访问的是/tmp/somedir/somefile。 对于多目主机上的tftpd,情况有点复杂。tftpd产生响应时用的源端口不是 69/UDP,而是一个随机端口,此时如果响应用的源IP不是请求用的目标IP,对于 客户端来说,这个响应不被接受。请用Wireshark抓包加强理解。 参看 tftp(1), inetd(8) -------------------------------------------------------------------------- 23.21 Debian上如何打开、关闭服务 Q: 在X界面下有ksysv命令可用,但我现在是远程telnet登录上来的,有无类似RetHat的 ntsysv的(n)curses界面的工具。 D: man update-rc.d 禁用samba服务: update-rc.d -f samba remove 启用samba服务: update-rc.d -f samba defaults D: 如果只是想禁用某个服务,用"dpkg --remove"、"apt-get remove"删除相关安装包 最省事,或者去/etc/rc2.d/目录下删除相应的以S打头的符号链接。 D: Debian的rcconf类似RedHat的ntsysv。rcconf只能禁用服务,不能启用服务,具体讲 就是禁用之后的服务不会出现在服务列表中。 不是所有服务都可以通过rcconf禁用,另有一些服务只能靠编辑/etc/inetd.conf来 禁用,kill -1 使之生效,比如telnet、tftp等服务。 23.22 Debian启动时在某一包含MTA字样的行停留了很长时间 A: 一般是系统上的sendmail或等价程序导致这种现象,MTA是Mail Transfer Agent的缩 写。如果不需要这类服务,用rcconf关闭它即可。 23.24 smbclient如何获取远程共享目录列表 A: touch /tmp/smb.conf smbclient -s /tmp/smb.conf -p 445 -L 192.168.7.20 -U "Administrator" Sharename Type Comment --------- ---- ------- ADMIN$ Disk 远程管理 C$ Disk 默认共享 IPC$ IPC 远程 IPC share Disk 23.25 如何查看当前物理内存大小 A: $ free -m total used free shared buffers cached Mem: 1012 193 819 0 10 121 total栏显示当前物理内存为1012MB。 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@nsfocus 无论是哪种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"建议过另一种邪门办法,下面在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@nsfocus 以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@nsfocus 以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()一次。 个人推荐严肃的*nix/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@nsfocus $ 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@nsfocus 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: scz@nsfocus 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: scz@nsfocus 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: scz@nsfocus 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@nsfocus 有人妄图创建套接字之后直接调用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) * mcs -d getsockname_test (only for SPARC/Solaris) */ #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开始计。 24.10 如何得到当前活动连接对应的本机IP地址 Q: 某些主机配有多块物理、虚拟网卡,比如安装了虚拟机、VPN、ADSL等等。这种情况 下gethostbyname()返回多个IP,第一个IP不见得是当前活动连接对应的本机IP。 A: puke@SMTH UNP 8.14小节介绍了如何利用路由表确定当前活动连接对应的本机IP地址: 1) 创建UDP套接字 2) connect()到目标IP,但不发送数据 3) getsockname()获取本机IP 24.11 C语言编程时如何在嵌入式汇编里使用宏 Q: 我在写一个C程序,其中用到了嵌入式汇编。有一些常数反复在嵌入式汇编里出现,C 语言#define的宏又不能用在嵌入式汇编里。 A: scz@nsfocus 2008-06-13 09:25 用法演示如下: __asm__ (" .set VALUE, 0x51211314 lis %r3, VALUE@h ori %r3, %r3, VALUE@l "); D: .equ好像与.set等价,测试无误。 D: scz@nsfocus 2009-04-20 13:55 $ as -v GNU assembler version 2.17 (mips-linux-gnu) using BFD version 2.17 Debian GNU/Linux $ gcc-2.95 -v gcc version 2.95.4 20011002 (Debian prerelease) 可以利用预处理程序: __asm__ (" #define VALUE 0x51211314 li $3, VALUE #undef VALUE "); 上述嵌入式汇编不能像下面这样直接编译: gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c 必须以如下步骤进行编译: gcc-2.95 -Wall -pipe -S -o mips_inline_asm.S mips_inline_asm.c gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.S 要点在于先动用-S生成扩展名为.S的文件,缺省情况下动用-S但不指定-o时将生成.s 文件。.S与.s有一个很重大的区别,编译.S时将再次调用预处理程序,然后才调用as, 编译.s时直接调用as。关于这些细节,可以在gcc-2.95命令行上指定-v获知。 这种办法有几个缺陷,一是必须先"-S"一次,显式生成.S,二是__asm__中#define的 宏名(不是宏值)不能以$打头,比如这样的宏定义编译不过去: __asm__ (" #define $MACRO $SOMETHING "); 下面的宏定义可以编译通过: __asm__ (" #define MACRO $SOMETHING "); 24.13 对于multihomed主机如何确定发往本机的UDP报文的目标IP Q: 一台multihomed主机,一个UDP套接字,绑定了0.0.0.0,一个发往该机的UDP报文被 接收到了,现在想知道这个UDP报文到达本机时的目标IP。 A: W. Richard Stevens 参看UNP 8.8、19.11、20.2、20.6小节。 1) recvfrom()无法满足这个需求。 通过recvmsg()函数使用IP_RECVDSTADDR。参看recvmsg(2)、cmsg(3)手册页。 此时为了确保multihomed主机响应报文的源IP与对端请求报文的目标IP一致,必须显 式调用bind()绑定相应IP,再sendmsg()/sendto()。 这存在可移植性方面的问题,一是winsock不支持recvmsg()函数,二是只有部分*nix 支持IP_RECVDSTADDR,比如Solaris 2.5就不支持IP_RECVDSTADDR。 如果OS不支持IP_RECVDSTADDR,可以尝试IP_RECVIF,这将获取接收到UDP报文的接口 的索引,然后间接获取所接收UDP报文的目标IP。 2) 另一个方案需要修改原始设计,即针对multihomed主机所有接口分别创建套接字并绑 定相应单播IP地址,用select()/poll()之类的函数等待这些套接字。 这个方案适用于winsock。另外一个好处是这将自动确保multihomed主机响应报文的 源IP与对端请求报文的目标IP一致。 24.14 MIPS/Debian上嵌入式汇编中自动插入nop充任delay slot Q: $ as -v GNU assembler version 2.17 (mips-linux-gnu) using BFD version 2.17 Debian GNU/Linux $ gcc-2.95 -v gcc version 2.95.4 20011002 (Debian prerelease) -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" text_begin: lui $3, %hi(0x51211314) jalr $3 /* * 我想让这条ori充任delay slot,结果编译下来,在jalr与ori之间自动插入 * 了nop指令充任delay slot,有办法禁止这个行为吗? */ ori $3, %lo(0x51211314) "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c $ gdb ./mips_inline_asm (gdb) x/4i text_begin 0x400748 : lui v1,0x5121 0x40074c : jalr v1 0x400750 : nop // 自动插入nop充任delay slot 0x400754 : ori v1,v1,0x1314 (gdb) 编译过程中as自动在jalr指令后面插入nop充任delay slot,但这个行为干挠了我的 实验,有无办法禁止这种自动插入nop充任delay slot的行为。 D: Nineveh@SMTH -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" text_begin: li $3, 0x51211314 jalr $3 /* * 我想让这条ori充任delay slot,结果编译下来,在jalr与ori之间自动插入 * 了nop指令充任delay slot,有办法禁止这个行为吗? */ ori $4, $0, 0x7F "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c $ gdb ./mips_inline_asm (gdb) x/5i text_begin 0x400748 : lui v1,0x5121 0x40074c : ori v1,v1,0x1314 0x400750 : jalr v1 0x400754 : nop // 自动插入nop充任delay slot 0x400758 : li a0,0x7f (gdb) 关于delay slot,填什么指令是汇编器决定的,写的时候把"ori $4, $0, 0x7F"放到 jalr前面,汇编出来的ori就会被放到delay slot里。 -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" text_begin: li $3, 0x51211314 ori $4, $0, 0x7F jalr $3 "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c $ gdb ./mips_inline_asm (gdb) x/4i text_begin 0x400748 : lui v1,0x5121 0x40074c : ori v1,v1,0x1314 0x400750 : jalr v1 0x400754 : li a0,0x7f // 就是那条"ori $4, $0, 0x7F",现在是伪指令形式 (gdb) A: scz@nsfocus 2009-04-20 15:02 由汇编器决定delay slot是什么无法满足我的原始需求,现在我需要精确控制每一条 汇编指令,即使我将错误的指令放在delay slot上也不能自动替我"修正"。 -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" text_begin: lui $3, 0x5121 /* * 我想让这条指令充任jalr的delay slot,若按Nineveh所述将该指令置于jalr * 之前,最终并不能达到目的。 */ ori $3, $3, 0x1314 jalr $3 "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c $ gdb ./mips_inline_asm (gdb) x/4i text_begin 0x400748 : lui v1,0x5121 0x40074c : ori v1,v1,0x1314 // 该指令未被放到delay slot上 0x400750 : jalr v1 0x400754 : nop (gdb) 我的原始需求动用".set noreorder"即可解决: -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" .set noreorder text_begin: lui $3, %hi(0x51211314) jalr $3 ori $3, %lo(0x51211314) .set reorder "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c $ gdb ./mips_inline_asm (gdb) x/3i text_begin 0x400748 : lui v1,0x5121 0x40074c : jalr v1 0x400750 : ori v1,v1,0x1314 // 这次没有自动插入nop 顺便说一句,我的原始实验如下: (gdb) r Program received signal SIGSEGV, Segmentation fault. 0x51210000 in ?? () (gdb) i r v1 pc v1: 0x51211314 pc: 0x51210000 jalr导致跳转进行中,然后位于delay slot的ori指令被执行,这个实验仅仅是想验 证妄图以此方式跳转到0x51211314是不可能的,delay slot被执行时跳转已在进行中, delay slot不能用来改变跳转的目标地址。 24.15 MIPS/Debian上嵌入式汇编中使用$a0这样的寄存器名 Q: $ as -v GNU assembler version 2.17 (mips-linux-gnu) using BFD version 2.17 Debian GNU/Linux $ gcc-2.95 -v gcc version 2.95.4 20011002 (Debian prerelease) -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" text_begin: /* * 这里$4写成$a0,编译时就说illegal operands,有办法让编译器认$a0吗? */ li $a0, 0x51211314 "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c {standard input}: Assembler messages: {standard input}:32: Error: illegal operands `li $a0,0x51211314' D: scz@nsfocus 2009-04-16 我有一个邪门解决方案,动用预处理程序。 -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" #define a0 $4 text_begin: /* * 这里写的是a0,不是$a0 */ li a0, 0x51211314 #undef a0 "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- 上述嵌入式汇编不能像下面这样直接编译: gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c 必须以如下步骤进行编译: gcc-2.95 -Wall -pipe -S -o mips_inline_asm.S mips_inline_asm.c gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.S A: Nineveh@SMTH 2009-04-17 升级as到2.18.x,开始支持$a0这样的寄存器名。 $ as -v GNU assembler version 2.18.0 (mips-linux-gnu) using BFD version (GNU Binutils for Debian) 2.18.0.20080103 $ gcc-2.95 -v gcc version 2.95.4 20011002 (Debian prerelease) -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" text_begin: /* * as 2.18.x开始支持$a0这样的寄存器名 */ li $a0, 0x51211314 "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -o mips_inline_asm mips_inline_asm.c D: scz@nsfocus 有时可能需要手工升级as: # apt-get update -u # dpkg -S `which as` binutils: /usr/bin/as # apt-cache show binutils ... ... Filename: pool/main/b/binutils/binutils_2.18.1~cvs20080103-7_mips.deb ... ... # wget http://ftp.debian.org/debian/pool/main/b/binutils/binutils_2.18.1~cvs20080103-7_mips.deb # dpkg -i binutils_2.18.1~cvs20080103-7_mips.deb 有时可能需要手工降级as: # wget http://ftp.debian.org/debian/pool/main/b/binutils/binutils_2.17-3_mips.deb # dpkg -i binutils_2.17-3_mips.deb 上述操作很可能导致apt-get命令工作不正常,除非你很擅长从错误中恢复或有过类 似处理经验,否则不建议使用"dpkg -i"直接安装。 24.16 relocation truncated to fit: R_MIPS_PC16 against `no symbol' D: scz@nsfocus 2009-04-22 14:21 $ as -v GNU assembler version 2.18.0 (mips-linux-gnu) using BFD version (GNU Binutils for Debian) 2.18.0.20080103 $ gcc-2.95 -v gcc version 2.95.4 20011002 (Debian prerelease) -------------------------------------------------------------------------- int main ( int argc, char * argv[] ) { __asm__ __volatile__ (" nop any_label: nop /* * 编写源代码时这里犯了一个低级错误,j后面没有写target */ j nop "); return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-2.95 -Wall -pipe -O0 -o mips_inline_asm mips_inline_asm.c /tmp/ccDoinnr.o: In function `any_label': mips_inline_asm.c:(.text+0x2c): relocation truncated to fit: R_MIPS_PC16 against `no symbol' collect2: ld returned 1 exit status 这是我简化后的情形,已经很容易发现问题根源。最初是写一个较复杂的嵌入式汇编 代码,中间某处j后面没有写target,编译、汇编阶段均未报错或警告,一直到链接 阶段来了一段类似上述输出的错误信息,折腾了半天,直至偶然发现j后无target。 在此记录备忘,以后排错时可参考。 24.17 gcc -pthread与gcc -lpthread有什么区别 D: scz man gcc -------------------------------------------------------------------------- -E 预处理结束后就停止,不进入编译阶段。向stdout输出预处理之后的源码。GCC 忽略那些不需要预处理的输入文件。 -dM 通知预处理器仅仅输出预处理结束时仍然有效的宏定义列表,与-E一起使用。 -------------------------------------------------------------------------- A: from stackoverflow.com $ echo | gcc-3.3 -E -dM - > singlethread $ echo | gcc-3.3 -E -dM -pthread - > multithread $ diff singlethread multithread 41a42 > #define _REENTRANT 1 $ cat multithread #define __DBL_MIN_EXP__ (-1021) #define __FLT_MIN__ 1.17549435e-38F #define __CHAR_BIT__ 8 #define __WCHAR_MAX__ 2147483647 #define __DBL_DENORM_MIN__ 4.9406564584124654e-324 #define __FLT_EVAL_METHOD__ 2 #define __unix__ 1 #define __i486__ 1 #define unix 1 #define __i386__ 1 #define __SIZE_TYPE__ unsigned int #define __ELF__ 1 #define __DBL_MIN_10_EXP__ (-307) #define __FINITE_MATH_ONLY__ 0 #define __GNUC_PATCHLEVEL__ 6 #define __FLT_RADIX__ 2 #define __LDBL_EPSILON__ 1.08420217248550443401e-19L #define __SHRT_MAX__ 32767 #define __LDBL_MAX__ 1.18973149535723176502e+4932L #define __linux 1 #define __unix 1 #define __LDBL_MAX_EXP__ 16384 #define __LONG_MAX__ 2147483647L #define __linux__ 1 #define __SCHAR_MAX__ 127 #define __DBL_DIG__ 15 #define __USER_LABEL_PREFIX__ #define linux 1 #define __STDC_HOSTED__ 1 #define __LDBL_MANT_DIG__ 64 #define __FLT_EPSILON__ 1.19209290e-7F #define __tune_i686__ 1 #define __LDBL_MIN__ 3.36210314311209350626e-4932L #define __WCHAR_TYPE__ long int #define __FLT_DIG__ 6 #define __FLT_MAX_10_EXP__ 38 #define __INT_MAX__ 2147483647 #define __i486 1 #define __gnu_linux__ 1 #define __FLT_MAX_EXP__ 128 #define __DECIMAL_DIG__ 21 #define _REENTRANT 1 #define __DBL_MANT_DIG__ 53 #define __WINT_TYPE__ unsigned int #define __GNUC__ 3 #define __LDBL_MIN_EXP__ (-16381) #define __LDBL_MAX_10_EXP__ 4932 #define __DBL_EPSILON__ 2.2204460492503131e-16 #define __DBL_MAX__ 1.7976931348623157e+308 #define __tune_pentiumpro__ 1 #define __DBL_MAX_EXP__ 1024 #define __FLT_DENORM_MIN__ 1.40129846e-45F #define __LONG_LONG_MAX__ 9223372036854775807LL #define __FLT_MAX__ 3.40282347e+38F #define __GXX_ABI_VERSION 102 #define __FLT_MIN_10_EXP__ (-37) #define __FLT_MIN_EXP__ (-125) #define i386 1 #define __GNUC_MINOR__ 3 #define __DBL_MAX_10_EXP__ 308 #define __LDBL_DENORM_MIN__ 3.64519953188247460253e-4951L #define __DBL_MIN__ 2.2250738585072014e-308 #define __PTRDIFF_TYPE__ int #define __LDBL_MIN_10_EXP__ (-4931) #define __REGISTER_PREFIX__ #define __LDBL_DIG__ 18 #define __NO_INLINE__ 1 #define __i386 1 #define __FLT_MANT_DIG__ 24 #define __VERSION__ "3.3.6 (Debian 1:3.3.6-15)" 可以看出,指定gcc编译选项-pthread(不是链接选项-lpthread)时,内置了宏定义: #define _REENTRANT 1 更精确地了解-pthread的行为: $ gcc-3.3 -dumpspecs | grep pthread %{posix:-D_POSIX_SOURCE} %{pthread:-D_REENTRANT} %{pthread:-lpthread} %{shared:-lc} %{!shared:%{mieee-fp:-lieee} %{profile:-lc_p}%{!profile:-lc}} 这意味着-pthread等价于"-D_REENTRANT -lpthread"。 对于x86/Linux平台上的GCC,推荐使用-pthread,而不是-lpthread。 D: 假设线程函数中使用了errno,如果能指定gcc编译选项-pthread最好,如果不能,一 般都有一些宏用于应对这种情况。 SPARC/Solaris -D_REENTRANT -lpthread AIX -D_THREAD_SAFE -lpthreads 24.18 传统Unix的DES型Passwd Hash是如何生成的 A: http://en.wikipedia.org/wiki/Crypt_(C) man 3 crypt 算法本身比wiki上讲的复杂,欲知细节,可查看OpenSSL源码: crypto\des\fcrypt.c DES_crypt()就是crypt()的实现代码。这种算法对salt有要求,必须是2个字符,取 值范围是[a-zA-Z0-9./]。如果提供的salt超出2个字符,自动截取前2个字符。 *nix上Python的crypt.crypt()封装了crypt(3)函数,Windows上Python没有crypt模 块。 -------------------------------------------------------------------------- #!/usr/bin/env python import sys, os, crypt if '__main__' == __name__ : i = len( sys.argv ) if i < 3 : print 'Usage: %s ' % os.path.basename( sys.argv[0] ) else: print crypt.crypt( sys.argv[1], sys.argv[2] ) -------------------------------------------------------------------------- $ ./DESPasswdHash.py 123456 e9 $ ./DESPasswdHash.py 123456 e9prWEu5B4FSs e9prWEu5B4FSs python -c 'import crypt;print crypt.crypt( "123456", "e9" )' perl -e 'print crypt( "123456", "e9" )."\n"' openssl passwd -salt e9 123456 e9prWEu5B4FSs D: 现代crypt(3)都支持如下形式的Passwd Hash: $id$salt$encrypted ID | Method ------------------------------------------------------------------------- 1 | MD5 2a | Blowfish (not in mainline glibc; added in some Linux distributions) 5 | SHA-256 (since glibc 2.7) 6 | SHA-512 (since glibc 2.7) 与DES型Passwd Hash不同,这些算法所用salt最短可以是1个字符。对不同算法salt 的长度上限是: MD5 | 8 characters SHA-256 | 16 characters SHA-512 | 16 characters encrypted的长度如下: MD5 | 22 characters SHA-256 | 43 characters SHA-512 | 86 characters salt和encrypted的取值范围是[a-zA-Z0-9./] python -c 'import crypt;print crypt.crypt( "123456", "$1$e9$" )' perl -e 'print crypt( "123456", "\$1\$e9\$" )."\n"' openssl passwd -1 -salt e9 123456 $1$e9$btCenk1ho9ylqZ.o6HVxC/ "openssl passwd"只能生成1型Passwd Hash,无法生成5、6型Passwd Hash。 python -c 'import crypt;print crypt.crypt( "123456", "$5$e9$" )' perl -e 'print crypt( "123456", "\$5\$e9\$" )."\n"' $5$e9$5h18u8GZKw0NkjTVO4gDI4/XXJ/e/94gYUUt0mDxRPA 24.21 TLS与动态库 D: scz@nsfocus 2013-11-15 17:28 不要在dlopen()加载的.so中定义__thread变量,无论从哪儿访问这些__thread变量。 这种用法会涉及dtv[i],历史上围绕dtv[i]产生了无数的BUG及重新实现的底层代码。 GLIBC内置的errno、__errno_location()、gethostbyname(3)引入的h_errno等等不 涉及dtv[i],不会使情况变得更复杂。 要点是"不要...定义...",而不是"不要...使用..."。换句话说,可以在dlopen()加 载的.so中使用在主程序中定义的__thread变量,此时不会涉及dtv[i],历史上的绝 大多数BUG都不会触发。 当然,__thread技术因其内部实现复杂且未标准化,与dtv[i]无关的已知、未知风险 仍然存在。比如tls_demo_4.c。 实在没办法时,可以考虑用TSD而不是TLS。不过即使是TSD也不推荐使用,毕竟使用 全局变量本身就不是什么好的编程方式。 24.22 GCC命令行选项 Q: 上哪儿查gcc -m32这种参数的意义? A: scz http://gcc.gnu.org/onlinedocs/gcc/index.html http://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html http://gcc.gnu.org/onlinedocs/gcc/Option-Index.html http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html -------------------------------------------------------------------------- -dM 通知预处理器仅仅输出预处理结束时仍然有效的宏定义列表,与-E一起使用。 -dumpspecs Print the compiler's built-in specs and don't do anything else. http://gcc.gnu.org/onlinedocs/gcc/Spec-Files.html -E 预处理结束后就停止,不进入编译阶段。向stdout输出预处理之后的源码。GCC 忽略那些不需要预处理的输入文件。 -m32 生成32-bits代码,int、long、指针都是32-bits,只能运行在x86上,不能运行 在x64上。 http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86-64-Options.html -------------------------------------------------------------------------- 24.23 printf()格式串 D: scz@nsfocus 2013-12-02 09:51 参printf(3) -------------------------------------------------------------------------- %.*s 这个实际是"%m.ns",此时m省略,n为*。*表示n值从参数中取,不是固定的。 %*s 这个实际是"%ms",m为*。如果*对应的值小于字符串长度,会按字符串实际长度 输出,m没有截断效应,只有对齐效应,因此"%*s"不等价于"%.*s"。 %m$.*n$s m指出s对应的参数位置,从1计,不包括格式串本身。n指出*对应的参数位置。 printf( "%.*s\n", width, str ); printf( "%2$.*s\n", width, str ); printf( "%1$.*2$s\n", str, width ); 这三种表达方式是等价的。 一般来说,用"%m$"代替"%",用"*n$"代替"*"。 %m 这是GLIBC扩展,相当于: printf( "%s", strerror( errno ) ); 不需要配套参数。 -------------------------------------------------------------------------- /* * gcc -pipe -O3 -o test test.c */ #include int main ( void ) { int width = 4; char *str = "0123456789"; printf( "%.*s\n", width, str ); printf( "%2$.*s\n", width, str ); printf( "%1$.*2$s\n", str, width ); printf( "%1$s %2$s\n", "November", "10" ); printf( "%2$s %1$s\n", "November", "10" ); printf( "%1$d times %1$d is %2$d\n", 10, 100 ); printf( "%1$.*2$s\n","Hello", 4 ); printf( "%2$.*1$s\n", 4, "Hello" ); return( 0 ); } -------------------------------------------------------------------------- $ ./test 0123 0123 0123 November 10 10 November 10 times 10 is 100 Hell Hell 24.24 /usr/include/features.h:323:26: bits/predefs.h: No such file or directory Q: gcc编译nsfocus_scan.c时碰上一个错误: In file included from /usr/include/stdio.h:28, /usr/include/features.h:323:26: bits/predefs.h: No such file or directory A: # aptitude install gcc-multilib 24.26 __builtin_expect()有什么用 A: scz@nsfocus 参看: http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html 函数原型如下: long __builtin_expect ( long x, long c ); 函数返回值就是x,函数语义是我们期望x等于c,以此向编译器提供分支预测信息。 比如: if ( __builtin_expect( x, 0 ) ) { foo(); } 我们期望x等于0,即我们不希望调用foo()。不考虑分支预测优化时即: if ( x ) { foo() } 再比如: if ( __builtin_expect( NULL != ptr, 1 ) ) { foo( *ptr ); } 我们期望ptr不为空。不考虑分支预测优化时即: if ( NULL != ptr ) { foo( *ptr ); } 将__builtin_expect( x, c )直接替换成x,忽略c没有任何问题。c的意义在于向编 译器表明,x等于c的可能性较大;编译器据此可以进行优化,以避免条件跳转指令对 CPU指令预取队列的刷新,从而调优性能。 参看: http://kernelnewbies.org/FAQ/LikelyUnlikely -------------------------------------------------------------------------- /* * gcc-4.7 -Wall -pipe -O3 -g3 -o builtin_expect_test builtin_expect_test.c */ #include #include #define likely(x) __builtin_expect(!!(x),1) #define unlikely(x) __builtin_expect(!!(x),0) static __attribute__((noinline)) unsigned int test_likely ( unsigned int x ) { if ( likely( 2 == x ) ) { x++; } else { x--; } return( x ); } /* end of test_likely */ static __attribute__((noinline)) unsigned int test_unlikely ( unsigned int x ) { if ( unlikely( 2 == x ) ) { x++; } else { x--; } return( x ); } /* end of test_unlikely */ int main ( int argc, char * argv[] ) { unsigned int x; if ( argc > 1 ) { x = ( unsigned int )strtoul( argv[1], NULL, 0 ); x = test_likely( x ); x = test_unlikely( x ); printf( "%#x\n", x ); } return( 0 ); } /* end of main */ -------------------------------------------------------------------------- $ gcc-4.7 -Wall -pipe -O3 -g3 -o builtin_expect_test builtin_expect_test.c $ objdump -d builtin_expect_test -------------------------------------------------------------------------- 08048490 : 8048490: 83 f8 02 cmp $0x2,%eax 8048493: 75 06 jne 804849b // 不等于2时条件跳转 8048495: b8 03 00 00 00 mov $0x3,%eax 804849a: c3 ret 804849b: 83 e8 01 sub $0x1,%eax 804849e: c3 ret 804849f: 90 nop 080484a0 : 80484a0: 83 f8 02 cmp $0x2,%eax 80484a3: 74 04 je 80484a9 // 等于2时条件跳转 80484a5: 83 e8 01 sub $0x1,%eax 80484a8: c3 ret 80484a9: b8 03 00 00 00 mov $0x3,%eax 80484ae: c3 ret 80484af: 90 nop -------------------------------------------------------------------------- 0x8048493处的jne指令是优化后的效果,程序员"自己认为"x很可能等于2,通过 likely( 2 == x )告知编译器,编译器产生的汇编指令只在x不等于2时条件跳转,其 余情况下不会刷新CPU指令预取队列。 这种优化依赖于程序员的判断,如果程序员的判断是错的,比如x实际上很大可能不 等于2,前述汇编指令反而导致性能下降。 从源码级看,将likely(x)、unlikely(x)直接替换成x没有任何问题。 __builtin_expect(x,c)、likely(x)、unlikely(x)的本质用途是在大多数情况下不 执行条件跳转指令,这三者的值都等于x,务必注意这点。 这种基于程序员判断的手动优化实在是鸡肋得很,首先非常影响代码的可阅读性;其 次现在新版本GCC的自动优化已经很强大了,像这次为了演示清晰,被迫用 __attribute__((noinline))来抵消一些优化;第三,绝大多数人写的代码用不上这 么精细的优化,有很多更直白的源码级低效写法存在,相比之下汇编指令级的优化可 以忽略。总体上讲,与其用这些trick,还不如在程序逻辑上多花点心思。 说点题外话,如果真到了追求CPU指令预取队列级优化、Cache级优化的地步,那得多 精耕细作的程序,得多牛X的程序员。这么妖娆的事儿,除了KERNEL和GLIBC,一般用 户态的程序还是不要了吧。 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总线)。 26. Python相关问题 26.0 Python如何强制回收内存 Q: 在循环体开始处建立了一些对象,循环体结尾处不再需要这些对象,每次循环会建立 新的对象。由于循环次数很多,导致内存耗尽,如何强制回收内存。 A: yuanmo@SMTH 2006-03-09 05:17:09 import gc gc.collect() 26.1 通过管道重定向标准输入时如何保持raw read Q: 测试环境是Python 2.4 For Windows。 > hexout "Grin: Grin\r\n" | python hexdump.py byteArray [ 11 bytes ] -> 16 bytes per line 00000000 47 72 69 6E 3A 20 47 72-69 6E 0A Grin: Grin. \r\n一起传给管道时,\r不见了! > hexout "\r" | python hexdump.py byteArray [ 1 bytes ] -> 16 bytes per line 00000000 0D . > hexout "\r\n" | python hexdump.py byteArray [ 1 bytes ] -> 16 bytes per line 00000000 0A . hexdump.py中用如下代码读取标准输入: buf = array( 'B', stdin.read() ) 通过管道重定向标准输入时如何保持raw read? A: flier > hexout "\r\n" | python -u hexdump.py byteArray [ 2 bytes ] -> 16 bytes per line 00000000 0D 0A .. 或者修改hexdump.py,增加如下代码: import msvcrt, os # # stdin/stdout/stderr # msvcrt.setmode( 0, os.O_BINARY ) msvcrt.setmode( 1, os.O_BINARY ) msvcrt.setmode( 2, os.O_BINARY ) 第二种方案更理想一些。 26.2 如何计算从"1970-01-01"到某日的间隔天数 A: qfp import datetime print datetime.date(2007,2,21)-datetime.date(1970,1,1) 13565 days, 0:00:00 26.3 显示Python库路径 A: baoz 2010-06-19 python -c "import sys;print sys.path" 26.4 re.findall()的非贪婪模式 Q: >>> re.findall(r'\[My\](.+)\[/My\]', r'[My]a[/My]\n[My]b[/My]') ['a', 'b'] >>> re.findall(r'\[My\](.+)\[/My\]', r'[My]a[/My]c[My]b[/My]') ['a[/My]c[My]b'] 原始意图是无论re.findall()第二形参是什么,都得到['a', 'b'],但现在第二条命 令没有进行最小匹配,请问有办法解决吗?其中c可以是任意字符、字符串。 A: dxh@nsfocus 对于*、+、?来说,默认是贪婪模式(greedy),在其后增加一个?号,则切换至非贪婪 模式,或者说进行最小匹配。 >>> re.findall(r'\[My\](.+?)\[/My\]', r'[My]a[/My]c[My]b[/My]') ['a', 'b'] 26.5 两个等长list之间的merge(paste)操作 Q: Unix有个paste命令,可以合并两个行数相等的文本文件。现有两个list: x=['a','b','c'] y=['d','e','f'] 我想得到新list: ['ad','be','cf'] 但我不想用for循环: x = ['a','b','c'] y = ['d','e','f'] n = len( x ) ret = [] for i in range( n ) : ret.append( x[i] + y[i] ) print ret A: scz@nsfocus 2011-12-27 12:46 可以用高序函数: map((lambda x,y:x+y),x,y) A: lotrpy@weibo 2011-12-27 19:07 [''.join(z) for z in zip(x,y)] 效率比map()低: c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "map((lambda x,y:x+y),x,y)" 100000 loops, best of 3: 2.1 usec per loop c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[''.join(z) for z in zip(x,y)]" 100000 loops, best of 3: 2.24 usec per loop A: hume 2011-12-28 18:41 [v+y[i] for i,v in enumerate(x)] 这个效率比前面的都高: c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[v+y[i] for i,v in enumerate(x)]" 1000000 loops, best of 3: 1.55 usec per loop A: lzx@nsfocus 2012-01-05 18:30 [x[i]+y[i] for i in range(len(x))] 这个和用enumerate效率差不多,不过这个似乎更容易想到一些。 c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[x[i]+y[i] for i in range(len(x))]" 1000000 loops, best of 3: 1.58 usec per loop A: zyh@nsfocus 2012-01-06 18:44 [i+j for i,j in zip(x,y)] c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[i+j for i,j in zip(x,y)]" 1000000 loops, best of 3: 1.94 usec per loop A: 请叫我李牛牛@weibo 2012-01-06 22:37 c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f'];L=len(x)" "[x[i]+y[i] for i in xrange(L)]" 1000000 loops, best of 3: 1.22 usec per loop c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[x[i]+y[i] for i in xrange(len(x))]" 1000000 loops, best of 3: 1.4 usec per loop c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f'];L=len(x)" "[x[i]+y[i] for i in range(L)]" 1000000 loops, best of 3: 1.46 usec per loop c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[x[i]+y[i] for i in range(len(x))]" 1000000 loops, best of 3: 1.58 usec per loop 可以看出,用xrange()代替range(),把len()移出for循环,效率提升明显,其中第 一条效率提升更多。 A: hw@nsfocus 2012-01-09 15:10 c:\Python27\python.exe -m timeit -s "x=['a','b','c'];y=['d','e','f'];from itertools import izip" "[i+j for i,j in izip(x,y)]" 1000000 loops, best of 3: 1.43 usec per loop 当x、y是小list时,用izip()没有xrange()快,当x、y是大list时,izip()效率优势 明显。 26.6 测试代码性能 A: lotrpy@weibo 2011-12-27 19:07 可以用timeit模块进行快速的性能测试: python -m timeit -s "x=['a','b','c'];y=['d','e','f']" "[''.join(z) for z in zip(x,y)]" 100000 loops, best of 3: 2.24 usec per loop A: scz@nsfocus python MergeListProfile.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- encoding: utf-8 -*- # # 进行性能对比测试。 # # 从Python Cookbook第1版"1.9 Finding the Intersection of Two Dictionaries" # 小节扒出来的代码,很有借鉴意义,用于测试各种函数的性能,直观显示对比结果。 # 入门必备技能! # import time import sys from itertools import izip # ########################################################################## # # # 返回值是浮点数,以s为单位。注意,循环了100000次,时间放大了100000倍。 # def TimeOfFunction ( function, num=100000 ) : def void() : pass # # end of void # time_a = time.clock() for i in range( num ) : void() # # end of for # time_b = time.clock() time_c = time.clock() for i in range( num ) : function() # # end of function # time_d = time.clock() # # 返回函数名以及执行时间,已经考虑了调度时间。 # return( function.__name__, ( time_d - time_c ) - ( time_b - time_a ) ) # # end of TimeOfFunction # # ########################################################################## # """ # # 如果用这组x、y进行测试,应该TimeOfFunction( function, num=1000000 )。此 # 时TargetFunc_7()只排第2。 # x = ['a','b','c'] y = ['d','e','f'] """ # # 如果用这组x、y进行测试,应该TimeOfFunction( function, num=100000 )。此时 # TargetFunc_7()最快。看上去,大数据量时更应该使用izip()。 # x = ['A' + str(i) for i in xrange(0,100)] y = ['B' + str(i) for i in xrange(0,100)] # ########################################################################## # # 不同的实现方式。 # # # from scz # def TargetFunc_0 () : return( map((lambda x,y:x+y),x,y) ) # # end of TargetFunc_0 # # # from lotrpy@weibo # def TargetFunc_1 () : return( [''.join(z) for z in zip(x,y)] ) # # end of TargetFunc_1 # # # normal # def TargetFunc_2 () : ret = [] n = len( x ) for i in range( n ) : ret.append( x[i] + y[i] ) # # end of for # return( ret ) # # end of TargetFunc_2 # # # from hume # def TargetFunc_3 () : return( [v+y[i] for i,v in enumerate(x)] ) # # end of TargetFunc_3 # # # from lzx@nsfocus # def TargetFunc_4 () : return( [x[i]+y[i] for i in range(len(x))] ) # # end of TargetFunc_4 # # # from zyh@nsfocus # def TargetFunc_5 () : return( [i+j for i,j in zip(x,y)] ) # # end of TargetFunc_5 # # # from 请叫我李牛牛@weibo # def TargetFunc_6 () : L = len( x ) # # range()会直接生成一个list,xrange()则不会,而是每次被调用时返回其中 # 的一个值,xrange()在循环中的性能比range()好,尤其是返回很大的list时, # 尽量用xrange()。 # return( [x[i]+y[i] for i in xrange(L)] ) # # end of TargetFunc_6 # # # from hw@nsfocus # def TargetFunc_7 () : return( [i+j for i,j in izip(x,y)] ) # # end of TargetFunc_7 # # ########################################################################## # # # 测试不同的TargetFunc_n()函数性能 # ret = {} funcs = \ [ TargetFunc_0, TargetFunc_1, TargetFunc_2, TargetFunc_3, TargetFunc_4, TargetFunc_5, TargetFunc_6, TargetFunc_7 ] # # 确保这些函数返回值一致。 # oldret = [] newret = [] for function in funcs : newret = function() if not oldret : # # 这里不需要动用copy.copy()或copy.deepcopy() # oldret = newret elif oldret != newret : print 'Checking %s()' % function.__name__ sys.exit( -1 ) # # end of for # # 循环10次计算平均值 # num = 10 for i in range( num ) : # print "Round[%u]" % i # for function in funcs : a, b = TimeOfFunction( function ) if a in ret : ret[a] += b else : ret[a] = b print "%s : %.9fs" % ( a, b ) # # end of for # # # end of for # print 'Average:' # # 2010-12-17 15:18 scz # # 对各函数运行时间进行降序排序并输出。map()第一形参是None时相当于zip()。 # result = zip( ret.values(), ret.keys() ) # # 出于空间上的考虑,sort()直接在原list所在空间上进行排序,就是说调用结束后 # result被改变。sort()不会返回新的list。 # result.sort( reverse=True ) for x in result : print "%s : %.9fs" % ( x[1], x[0] / num ) # # end of for # -------------------------------------------------------------------------- 假设: x = ['a','b','c'] y = ['d','e','f'] num = 1000000 Average: TargetFunc_2 : 2.313999329s TargetFunc_1 : 2.298966419s TargetFunc_0 : 2.185276983s TargetFunc_5 : 2.009906884s TargetFunc_4 : 1.765377419s TargetFunc_3 : 1.676222731s TargetFunc_7 : 1.586907511s TargetFunc_6 : 1.529695776s 假设: x = ['A' + str(i) for i in xrange(0,100)] y = ['B' + str(i) for i in xrange(0,100)] num = 100000 Average: TargetFunc_1 : 3.966280626s TargetFunc_2 : 3.902535827s TargetFunc_0 : 3.218697966s TargetFunc_5 : 2.759791314s TargetFunc_3 : 2.592151199s TargetFunc_4 : 2.578258690s TargetFunc_6 : 2.572227805s TargetFunc_7 : 2.149801880s 当x、y是大list时,izip()效率优势明显,比enumerate()、xrange()都快。 A: hw@nsfocus 2012-01-09 15:10 python MergeListProfileOther.py -------------------------------------------------------------------------- #!/usr/bin/env python # -*- encoding: utf-8 -*- # # hw@nsfocus 2012-01-09 15:10 # # 进行性能对比测试。 # import sys import timeit from itertools import izip # ########################################################################## # # # 返回值是浮点数,以s为单位。注意,循环了100000次,时间放大了100000倍。 # def TimeOfFunction ( fname, num=100000 ) : t = timeit.Timer( '%s()' % fname, 'from __main__ import %s' % fname ) # # 返回函数名以及执行时间。 # return( fname, t.timeit( number=num ) ) # # end of TimeOfFunction # # ########################################################################## # """ # # 如果用这组x、y进行测试,应该TimeOfFunction( function, num=1000000 )。此 # 时TargetFunc_7()只排第2。 # x = ['a','b','c'] y = ['d','e','f'] """ # # 如果用这组x、y进行测试,应该TimeOfFunction( function, num=100000 )。此时 # TargetFunc_7()最快。看上去,大数据量时更应该使用izip()。 # x = ['A' + str(i) for i in xrange(0,100)] y = ['B' + str(i) for i in xrange(0,100)] # ########################################################################## # # 不同的实现方式。 # # # from scz # def TargetFunc_0 () : return( map((lambda x,y:x+y),x,y) ) # # end of TargetFunc_0 # # # from lotrpy@weibo # def TargetFunc_1 () : return( [''.join(z) for z in zip(x,y)] ) # # end of TargetFunc_1 # # # normal # def TargetFunc_2 () : ret = [] n = len( x ) for i in range( n ) : ret.append( x[i] + y[i] ) # # end of for # return( ret ) # # end of TargetFunc_2 # # # from hume # def TargetFunc_3 () : return( [v+y[i] for i,v in enumerate(x)] ) # # end of TargetFunc_3 # # # from lzx@nsfocus # def TargetFunc_4 () : return( [x[i]+y[i] for i in range(len(x))] ) # # end of TargetFunc_4 # # # from zyh@nsfocus # def TargetFunc_5 () : return( [i+j for i,j in zip(x,y)] ) # # end of TargetFunc_5 # # # from 请叫我李牛牛@weibo # def TargetFunc_6 () : L = len( x ) # # range()会直接生成一个list,xrange()则不会,而是每次被调用时返回其中 # 的一个值,xrange()在循环中的性能比range()好,尤其是返回很大的list时, # 尽量用xrange()。 # return( [x[i]+y[i] for i in xrange(L)] ) # # end of TargetFunc_6 # # # from hw@nsfocus # def TargetFunc_7 () : return( [i+j for i,j in izip(x,y)] ) # # end of TargetFunc_7 # # ########################################################################## # # # 测试不同的TargetFunc_n()函数性能 # if '__main__' == __name__ : this = sys.modules['__main__'] # # 确保这些函数返回值一致。 # oldret = [] newret = [] for f in dir( this ) : if callable( getattr( this, f, None ) ) and f.startswith( 'TargetFunc_' ) : newret = eval( '%s()' % f ) if not oldret : # # 这里不需要动用copy.copy()或copy.deepcopy() # oldret = newret elif oldret != newret : print 'Checking %s()' % f sys.exit( -1 ) # # end of for # ret = {} # # 循环10次计算平均值 # num = 10 for i in range( num ) : # print "Round[%u]" % i # for f in dir( this ) : if callable( getattr( this, f, None ) ) and f.startswith( 'TargetFunc_' ) : a, b = TimeOfFunction( f ) if a in ret : ret[a] += b else : ret[a] = b print "%s : %.9fs" % ( a, b ) # # end of for # # # end of for # print 'Average:' # # 2010-12-17 15:18 scz # # 对各函数运行时间进行降序排序并输出。map()第一形参是None时相当于zip()。 # result = zip( ret.values(), ret.keys() ) # # 出于空间上的考虑,sort()直接在原list所在空间上进行排序,就是说调用结束后 # result被改变。sort()不会返回新的list。 # result.sort( reverse=True ) for x in result : print "%s : %.9fs" % ( x[1], x[0] / num ) # # end of for # -------------------------------------------------------------------------- 假设: x = ['A' + str(i) for i in xrange(0,100)] y = ['B' + str(i) for i in xrange(0,100)] num = 100000 Average: TargetFunc_1 : 3.931241167s TargetFunc_2 : 3.912196821s TargetFunc_0 : 3.108840062s TargetFunc_5 : 2.775525899s TargetFunc_3 : 2.744446860s TargetFunc_4 : 2.742812149s TargetFunc_6 : 2.646931505s TargetFunc_7 : 2.190302298s MergeListProfile.py的TimeOfFunction()是自己实现的,MergeListProfileOther.py 的TimeOfFunction()用了timeit模块。从最终结果看,自己实现的TimeOfFunction() 相比timeit模块,没有额外的效率损耗,两种办法都可以用。 A: hw@nsfocus 2012-01-10 15:02 参: http://code.google.com/p/flot/ 可以使用Flot画图: python plot.py 1 30 1 1_30.html -------------------------------------------------------------------------- #!/usr/bin/env python # -*- encoding: utf-8 -*- # # hw@nsfocus 2012-01-10 15:02 # # 进行性能对比测试,输出HTML。 # # python plot.py 1 30 1 1_30.html # import sys, os, timeit, gc from itertools import izip # ########################################################################## # # # 返回值是浮点数,以s为单位。注意,循环了100000次,时间放大了100000倍。 # def TimeOfFunction ( fname, num=100000 ) : t = timeit.Timer( '%s()' % fname, 'from __main__ import %s' % fname ) # # 返回函数名以及执行时间。 # return( fname, t.timeit( number=num ) ) # # end of TimeOfFunction # # ########################################################################## # x = ['a','b','c'] y = ['d','e','f'] # ########################################################################## # def generate_xy ( n ) : x = ['A' + str(i) for i in xrange(0,n)] y = ['B' + str(i) for i in xrange(0,n)] return( x, y ) # # end of generate_xy # def build_jscript ( data, base, step ) : template = \ """%s var options = { legend: { position: "nw" } }; $.plot ( $( "#placeholder" ), [ %s ], options );""" xxx = [] for fname in data : xxx.append( 'var %s = %s;' % ( fname, [[i*step+base,v] for i,v in enumerate(data[fname])] ) ) # # end of data # xxx = '\n'.join( xxx ) yyy = [] for fname in data : yyy.append( '{ label:"%s", data:%s }' % ( fname, fname ) ) # # end of for # yyy = ',\n'.join( yyy ) return( template % ( xxx, yyy ) ) # # end of build_jscript # def build_html ( jscript ) : template = \ """ Merge List Profile

Merge List Profile

""" return( template % jscript ) # # end of build_html # # ########################################################################## # # 不同的实现方式。 # # # from scz # def TargetFunc_0 () : return( map((lambda x,y:x+y),x,y) ) # # end of TargetFunc_0 # # # from lotrpy@weibo # def TargetFunc_1 () : return( [''.join(z) for z in zip(x,y)] ) # # end of TargetFunc_1 # # # normal # def TargetFunc_2 () : ret = [] n = len( x ) for i in range( n ) : ret.append( x[i] + y[i] ) # # end of for # return( ret ) # # end of TargetFunc_2 # # # from hume # def TargetFunc_3 () : return( [v+y[i] for i,v in enumerate(x)] ) # # end of TargetFunc_3 # # # from lzx@nsfocus # def TargetFunc_4 () : return( [x[i]+y[i] for i in range(len(x))] ) # # end of TargetFunc_4 # # # from zyh@nsfocus # def TargetFunc_5 () : return( [i+j for i,j in zip(x,y)] ) # # end of TargetFunc_5 # # # from 请叫我李牛牛@weibo # def TargetFunc_6 () : L = len( x ) # # range()会直接生成一个list,xrange()则不会,而是每次被调用时返回其中 # 的一个值,xrange()在循环中的性能比range()好,尤其是返回很大的list时, # 尽量用xrange()。 # return( [x[i]+y[i] for i in xrange(L)] ) # # end of TargetFunc_6 # # # from hw@nsfocus # def TargetFunc_7 () : return( [i+j for i,j in izip(x,y)] ) # # end of TargetFunc_7 # # ########################################################################## # # # 测试不同的TargetFunc_n()函数性能 # if '__main__' == __name__ : i = len( sys.argv ) if i < 4 : print 'Usage: %s ' % os.path.basename( sys.argv[0] ) sys.exit( -1 ) else : min = int( sys.argv[1], 0 ) max = int( sys.argv[2], 0 ) + 1 step = int( sys.argv[3], 0 ) if i < 5 : fname = 'plot.html' else : fname = sys.argv[4] this = sys.modules['__main__'] # # 确保这些函数返回值一致。 # oldret = [] newret = [] for f in dir( this ) : if callable( getattr( this, f, None ) ) and f.startswith( 'TargetFunc_' ) : newret = eval( '%s()' % f ) if not oldret : # # 这里不需要动用copy.copy()或copy.deepcopy() # oldret = newret elif oldret != newret : print 'Checking %s()' % f sys.exit( -1 ) # # end of for # ret = {} for i in xrange( min, max, step ) : print "Round[%u]" % i x,y = generate_xy( i ) gc.collect() gc.disable() for f in dir( this ) : if callable( getattr( this, f, None ) ) and f.startswith( 'TargetFunc_' ) : a, b = TimeOfFunction( f ) if a not in ret : ret[a] = [] ret[a].append( b ) print "%s : %.9fs" % ( a, b ) # # end of for # gc.enable() # # end of for # jscript = build_jscript( ret, min, step ) html = build_html( jscript ) file( fname, 'w' ).write( html ) -------------------------------------------------------------------------- 用浏览器打开plot.py生成的HTML,即可得到性能对比曲线。我们测试了10、20、30、 50、200共五种长度的list,izip的优势明显,enumerate、xrange第二梯队,range、 zip第三梯队,map第四梯队,for+append、zip+join第五梯队。 26.7 如何把一个字符串反序 A: x = "0123456789" print x[::-1] 9876543210 这个办法比''.join(reversed(x))要快。 python -m timeit -s "x='0123456789'" "x[::-1]" 1000000 loops, best of 3: 0.201 usec per loop python -m timeit -s "x='0123456789'" "''.join(reversed(x))" 1000000 loops, best of 3: 0.942 usec per loop 26.8 如何确认Python的版本 A: import sys sys.version 想知道某模块的版本,类似: import pycurl pycurl.version 26.9 如何查看某模块的源代码 Q: 我想查看crypt.crypt()的源码实现 A: import crypt crypt.__file__ '/usr/lib/python2.7/lib-dynload/crypt.so' help(crypt) /usr/lib/python2.7/lib-dynload/crypt.so http://docs.python.org/library/crypt import inspect inspect.getfile(crypt) '/usr/lib/python2.7/lib-dynload/crypt.so' inspect.getmodule(crypt) http://svn.python.org/projects/python/trunk/ http://svn.python.org/projects/python/trunk/Modules/cryptmodule.c http://hg.python.org/cpython/file/2.6/ http://hg.python.org/cpython/file/2.7/ http://hg.python.org/cpython/file/.../Modules/cryptmodule.c A: mkdir python cd python apt-get source python2.6 下载源码包并存放在当前目录下。 ./python2.6-2.6.6/Modules/cryptmodule.c D: Python的crypt.crypt()就是封装了*nix上的crypt(3)函数。 $ dpkg -S /usr/lib/libcrypto.a libssl-dev: /usr/lib/libcrypto.a mkdir libssl-dev cd libssl-dev apt-get source libssl-dev 这就是OpenSSL的源码。 26.10 如何将一段16进制字节流转换成可打印字符 A: $ python -c 'import binascii;print binascii.unhexlify("746573745F646F6D61696E")' test_domain 27. Wireshark相关问题 27.0 如何得到IBM AppScan的规则升级包下载链接 A: Capture Filter : host updates.installshield.com and tcp port 80 Display Filter : tcp contains Rules-Update- 最后结果形如: http://download4.boulder.ibm.com/sar/CMA/RAA/02d9w/0/Rules-Update-752.exe 27.1 捕捉TCP RST报文 A: Capture Filter : 4 = tcp[13] & 4 FIN 1 SYN 2 RST 4 PSH 8 ACK 0x10 URG 0x20 27.2 Wireshark 1.8.x的Capture Filter在哪儿 Q: Wireshark的UI设计人员脑袋被驴踢了还是被门夹了,Capture Fitler上哪儿去了? A: Capture->Options->双击目标网卡->Capture Filter 据说是Wireshark 1.8开始支持同时在多块网卡上抓包,每块网卡有自己的Capture Filter,因此出现了这种UI上的变化。但其帮助手册里的截图没有同步更新,从用户 习惯上讲,这个UI变化真地无法评述。 28. WEB前端安全相关问题 28.0 如何在网页上自动提交POST请求 D: andfarm 2012-11-27
...