免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1791 | 回复: 0
打印 上一主题 下一主题

strace 与重定向操作符 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-04-09 21:31 |只看该作者 |倒序浏览
原理:
strace首先fork,然后子进程先自动PTRACE_TRACEME,然后execve被调试的文件,等待父
进程的调试。父进程进入一个循环,用wait4等待子进程,并判断子进程的状态,退出状
态有几种 - 子进程的信号被截获、子进程系统调用之前与之后、子进程退出。然后按照
情况将截获的信息打印在屏幕上。之后,继续调用PTRACE_SYSCALL使程序继续执行。
例子:
strace /bin/bash -c ls 命令的结果可以通过“%>”的形式来定向输出,%表示文件描述符:1为标准输出stdout、2为标准错误
stderr。系统默认%值是1,也就是“1>”,而1>可以简写为>,也就是默认为>。stdout的默认目标是终端,stderr的默认目标为也是终端。我们在批处理中执行: echo text >result.txt ,我们就可以在屏幕上会看到 echo
text 1>result.txt ,即是这个道理。
句柄            句柄的数字代号            描述                                                STDIN
                                    0
                                    键盘输入
                                                    STDOUT
                                    1
                                    输出到命令提示符窗口
                                                    STDERR
                                    2
                                    错误输出到命令提示符窗口
                                                    UNDEFINED
                                    3-9
                                    句柄由应用程序单独定义,它们是各个工具特有的
                        


字 0 到 9 代表前 10 个句柄。可以使用命令 Cmd.exe 运行程序,并对该程序前 10
个句柄中的任何一个句柄进行重定向。要指定要用的句柄,请在重定向操作符之前键入该句柄的数字。如果未定义句柄,则默认的  重定向输出操作符是 1。键入
操作符之后,必须指定数据的读写位置。可以指定文件名或其他现有的句柄。
要指定重定向到现有句柄,请使用与 (&) 字符,后面接要重定向的句柄号(即 &句柄号)。例如,下面的命令可以将句柄 2(即 STDERR)重定向到句柄 1(即 STDOUT):
2>&1
下表列出了可用于重定向输入和输出数据流的操作符。
                                                     重定向操作符            描述                                                >
                                    将命令输出写入到文件或设备(如打印机),而不是命令提示符窗口或句柄。
                                                                            从文件而不是从键盘或句柄读入命令输入。
                                                    >>
                                    将命令输出添加到文件末尾而不删除文件中已有的信息。
                                                    >&
                                    将一个句柄的输出写入到另一个句柄的输入中。
                                                                            从一个句柄读取输入并将其写入到另一个句柄输出中。
                                                    |
                                    从一个命令中读取输出并将其写入另一个命令的输入中。也称作管道。
                        

默认情况下,可以从键盘将命令输入(即 STDIN 句柄)发送到 Cmd.exe,然后由 Cmd.exe 将命令输出(即 STDOUT 句柄)发送到命令提示符窗口。
重定向输入 (
要通过键盘将输入重定向到文件或设备,请使用                         •                        
0 是            
重定向输出 (>)
几乎所有的命令都将输出发送到命令提示符窗口。即使将输出发送到驱动器或打印机的命令也会在命令提示符窗口显示消息和提示。
要将输出从命令提示符窗口重定向到文件或设备,请使用 > 操作符。可以在许多命令中使用该操作符。例如,要将 dir 输出重定向到 Dirlist.txt,请键入:
dir>dirlist.txt
如果 Dirlist.txt 不存在,Cmd.exe 将创建该文件。如果 Dirlist.txt 存在,Cmd.exe 将使用 dir 命令的输出替换文件中的信息。
要运行 netsh routing dump 命令,然后将输出发送到 Route.cfg,请键入:
netsh routing dump>c:\route.cfg
> 操作符可以打开具有只写访问权限的指定文件。因此,不能使用该操作符读取文件。例如,如果使用重定向操作符 >&0 启动程序,则写入句柄 1 的所有尝试操作都将失败,因为句柄 0 最初是以只读访问方式打开的。
注意
                        •                        
1 是 > 重定向输出操作符的默认句柄。
                        
复制句柄
重定向操作符 & 可以将输出或输入从一个指定句柄复制到另一个指定的句柄。例如,要将 dir 输出发送到 File.txt 并将错误输出发送到 File.txt,请键入:
dir>c:\file.txt 2>&1
复制句柄时,可以复制该句柄原状态的所有特性。例如,如果一个句柄具有只读访问的属性,则该句柄的所有副本都具有只读访问属性。不能将一个具有只读访问属性的句柄复制到另一个具有只写访问属性的句柄。
使用 & 操作符重定向输入和副本

将重定向输入操作符 (search.txt 2使用 & 操作符重定向输出和复制
如果将输出重定向到文件且指定了现有的文件名,Cmd.exe 将以只写方式打开文件并覆盖该文件内容。如果指定了句柄,Cmd.exe 将文件复制到现有句柄中。
要将用户定义的句柄 3 复制到句柄 1,请键入:
>&3
要将包括句柄 2(即 STDERR)的所有输出从 ipconfig 命令重定向到句柄 1(即 STDOUT),然后将输出重定向到 Output.log,请键入:
ipconfig.exe>>output.log 2>&1
使用 >> 重定向操作符附加输出
要从命令中将输出添加到文件末尾而不丢失文件中已存在的任何信息,请使用两个连续的大于号(即 >>)。例如,使用下列命令可以将 dir 命令生成的目录列表附加到 Dirlist.txt 文件:
dir>>dirlist.txt
要将 netstat 命令的输出附加到 Tcpinfo.txt 的末尾,请键入:
netstat>>tcpinfo.txt
使用管道操作符 (|)
管道操作符 (|) 可以提取一个命令的输出(默认情况下是 STDOUT),然后将其定向到另一个命令的输入(默认情况下是 STDIN)中。例如,使用下面的命令可以对目录进行分类:
dir | sort
在本例中,将同时启动两个命令,但随后 sort 命令会暂停,直到它接收到 dir 命令的输出为止。sort 命令使用 dir 命令的输出作为输入,然后将输出发送到句柄 1(即 STDOUT)。
合并带重定向操作符的命令
通过合并带有其他命令和文件名的筛选器命令,可以创建自定义命令。例如,可以使用以下命令存储包含“LOG”字符串的文件名:
dir /b | find "log" loglist.txt
dir 命令的输出是通过 find 筛选器命令进行发送的。包含字符串“LOG”的文件名作为文件名列表(例如,NetshConfig.log、Logdat.svd 和 Mylog.bat)存储在文件 Loglist.txt 中。
要在相同命令中使用多个筛选器,请使用管道 (|) 分隔筛选器。例如,下面的命令将搜索 C 盘上的每个目录以查找包含“LOG”字符串的文件名,并且在命令提示符窗口中每次显示一屏:
dir c:\ /s /b | find "log" | more
利用管道 (|) 可以对 Cmd.exe 进行定向,使其通过 find 筛选器命令发送 dir 命令输出。find 命令只选择包含字符串“LOG”的文件名。more 命令可以显示 find 命令选择的文件名(在命令提示符窗口中每次显示一屏)。
strace命令选项解析:

调用:  
strace [ -dffhiqrtttTvxx ] [ -acolumn ] [ -eexpr ] ...  
[ -ofile ] [ -ppid ] ... [ -sstrsize ] [ -uusername ] [ command [ arg ... ] ]  

strace -c [ -eexpr ] ... [ -Ooverhead ] [ -Ssortby ] [ command [ arg ... ] ]  
功能:  
跟踪程式执行时的系统调用和所接收的信号.通常的用法是strace执行一直到commande结束.  
并且将所调用的系统调用的名称、参数和返回值输出到标准输出或者输出到-o指定的文件.  
strace是一个功能强大的调试,分析诊断工具.你将发现他是一个极好的帮手在你要调试一个无法看到源码或者源码无法在编译的程序.  
你将轻松的学习到一个软件是如何通过系统调用来实现他的功能的.而且作为一个程序设计师,你可以了解到在用户态和内核态是如何通过系统调用和信号来实现程序的功能的.  
strace的每一行输出包括系统调用名称,然后是参数和返回值.这个例子:  
strace cat /dev/null  
他的输出会有:  
open(\\"/dev/null\\",O_RDONLY) = 3  
有错误产生时,一般会返回-1.所以会有错误标志和描述:  
open(\\"/foor/bar\\",)_RDONLY) = -1 ENOENT (no such file or directory)  
信号将输出喂信号标志和信号的描述.跟踪并中断这个命令\\"sleep 600\\":  
sigsuspend({}  
--- SIGINT (Interrupt) ---  
+++ killed by SIGINT +++  
参数的输出有些不一致.如shell命令中的 \\">>tmp\\",将输出:  
open(\\"tmp\\",O_WRONLY|O_APPEND|A_CREAT,0666) = 3  
对于结构指针,将进行适当的显示.如:\\"ls -l /dev/null\\":  
lstat(\\"/dev/null\\",{st_mode=S_IFCHR|0666},st_rdev=makdev[1,3],...}) = 0  
请注意\\"struct stat\\" 的声明和这里的输出.lstat的第一个参数是输入参数,而第二个参数是向外传值.  
当你尝试\\"ls -l\\" 一个不存在的文件时,会有:  
lstat(/foot/ball\\",0xb004) = -1 ENOENT (no such file or directory)  
char*将作为C的字符串类型输出.没有字符串输出时一般是char* 是一个转义字符,只输出字符串的长度.  
当字符串过长是会使用\\"...\\"省略.如在\\"ls -l\\"会有一个gepwuid调用读取password文件:  
read(3,\\"root::0:0:System Administrator:/\\"...,1024) = 422  
当参数是结构数组时,将按照简单的指针和数组输出如:  
getgroups(4,[0,2,4,5]) = 4  
关于bit作为参数的情形,也是使用方括号,并且用空格将每一项参数隔开.如:  
sigprocmask(SIG_BLOCK,[CHLD TTOU],[]) = 0  
这里第二个参数代表两个信号SIGCHLD 和 SIGTTOU.如果bit型参数全部置位,则有如下的输出:  
sigprocmask(SIG_UNBLOCK,~[],NULL) = 0  
这里第二个参数全部置位.  

参数说明:  
-c 统计每一系统调用的所执行的时间,次数和出错的次数等.  
-d 输出strace关于标准错误的调试信息.  
-f 跟踪由fork调用所产生的子进程.  
-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.  
-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.  
-h 输出简要的帮助信息.  
-i 输出系统调用的入口指针.  
-q 禁止输出关于脱离的消息.  
-r 打印出相对时间关于,,每一个系统调用.  
-t 在输出中的每一行前加上时间信息.  
-tt 在输出中的每一行前加上时间信息,微秒级.  
-ttt 微秒级输出,以秒了表示时间.  
-T 显示每一调用所耗的时间.  
-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.  
-V 输出strace的版本信息.  
-x 以十六进制形式输出非标准字符串  
-xx 所有字符串以十六进制形式输出.  
-a column  
设置返回值的输出位置.默认为40.  
-e expr  
指定一个表达式,用来控制如何跟踪.格式如下:  
[qualifier=][!]value1[,value2]...  
qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的qualifier是 trace.感叹号是否定符号.例如:  
-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.  
注意有些shell使用!来执行历史记录里的命令,所以要使用\\\\.  
-e trace=set  
只跟踪指定的系统调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.  
-e trace=file  
只跟踪有关文件操作的系统调用.  
-e trace=process  
只跟踪有关进程控制的系统调用.  
-e trace=network  
跟踪与网络有关的所有系统调用.  
-e strace=signal  
跟踪所有与系统信号有关的系统调用  
-e trace=ipc  
跟踪所有与进程通讯有关的系统调用  
-e abbrev=set  
设定strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.  
-e raw=set  
将指定的系统调用的参数以十六进制显示.  
-e signal=set  
指定跟踪的系统信号.默认为all.如signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.  
-e read=set  
输出从指定文件中读出的数据.例如:  
-e read=3,5  
-e write=set  
输出写入到指定文件中的数据.  
-o filename  
将strace的输出写入文件filename  
-p pid  
跟踪指定的进程pid.  
-s strsize  
指定输出的字符串的最大长度.默认为32.文件名一直全部输出.  
-u username  
以username的UID和GID执行被跟踪的命令.  
  

  
用strace调试程序
      在理想世界里,每当一个程序不能正常执行一个功能时,它就会给出一个有用的错误提示,告诉你在足够的改正错误的线索。但遗憾的是,我们不是生活在理想世界里,起码不总是生活在理想世界里。有时候一个程序出现了问题,你无法找到原因。
这就是调试程序出现的原因。strace是一个必不可少的调试工具,strace用来监视系统调用。你不仅可以调试一个新开始的程序,也可以调试一个已经在运行的程序(把strace绑定到一个已有的PID上面)。
首先让我们看一个真实的例子:
[BOLD]启动KDE时出现问题[/BOLD]
前一段时间,我在启动KDE的时候出了问题,KDE的错误信息无法给我任何有帮助的线索。
代码:
_KDE_IceTransSocketCreateListener: failed to bind listener
_KDE_IceTransSocketUNIXCreateListener: ...SocketCreateListener() failed
_KDE_IceTransMakeAllCOTSServerListeners: failed to create listener for local

Cannot establish any listening sockets DCOPServer self-test failed.
  


对我来说这个错误信息没有太多意义,只是一个对KDE来说至关重要的负责进程间通信的程序无法启动。我还可以知道这个错误和ICE协议(Inter Client Exchange)有关,除此之外,我不知道什么是KDE启动出错的原因。

我决定采用strace看一下在启动dcopserver时到底程序做了什么:
代码:
strace -f -F -o ~/dcop-strace.txt dcopserver
  


这里 -f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/dcop-strace.txt里面,dcopserver是要启动和调试的程序。

再次出现错误之后,我检查了错误输出文件dcop-strace.txt,文件里有很多系统调用的记录。在程序运行出错前的有关记录如下:
代码:
27207 mkdir("/tmp/.ICE-unix", 0777) = -1 EEXIST (File exists)
27207 lstat64("/tmp/.ICE-unix", {st_mode=S_IFDIR|S_ISVTX|0755, st_size=4096, ...}) = 0
27207 unlink("/tmp/.ICE-unix/dcop27207-1066844596") = -1 ENOENT (No such file or directory)
27207 bind(3, {sin_family=AF_UNIX, path="/tmp/.ICE-unix/dcop27207-1066844596"}, 3 = -1 EACCES (Permission denied)  
27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketCreateListener: failed to "..., 46) = 46
27207 close(3) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "SocketUNIXCreateListener: ...Soc"..., 59) = 59
27207 umask(0) = 0 27207 write(2, "_KDE_IceTrans", 13) = 13
27207 write(2, "MakeAllCOTSServerListeners: fail"..., 64) = 64
27207 write(2, "Cannot establish any listening s"..., 39) = 39
  



中第一行显示程序试图创建/tmp/.ICE-unix目录,权限为0777,这个操作因为目录已经存在而失败了。第二个系统调用(lstat64)检查
了目录状态,并显示这个目录的权限是0755,这里出现了第一个程序运行错误的线索:程序试图创建属性为0777的目录,但是已经存在了一个属性为
0755的目录。第三个系统调用(unlink)试图删除一个文件,但是这个文件并不存在。这并不奇怪,因为这个操作只是试图删掉可能存在的老文件。


是,第四行确认了错误所在。他试图绑定到/tmp/.ICE-unix/dcop27207-1066844596,但是出现了拒绝访问错误。.
ICE_unix目录的用户和组都是root,并且只有所有者具有写权限。一个非root用户无法在这个目录下面建立文件,如果把目录属性改成0777,
则前面的操作有可能可以执行,而这正是第一步错误出现时进行过的操作。

所以我运行了chmod 0777 /tmp/.ICE-unix之后KDE就可以正常启动了,问题解决了,用strace进行跟踪调试只需要花很短的几分钟时间跟踪程序运行,然后检查并分析输出文件。
说 明:运行chmod 0777只是一个测试,一般不要把一个目录设置成所有用户可读写,同时不设置粘滞位(sticky
bit)。给目录设置粘滞位可以阻止一个用户随意删除可写目录下面其他人的文件。一般你会发现/tmp目录因为这个原因设置了粘滞位。KDE可以正常启动
之后,运行chmod +t /tmp/.ICE-unix给.ICE_unix设置粘滞位。

[BOLD]解决库依赖问题[/BOLD]
starce
的另一个用处是解决和动态库相关的问题。当对一个可执行文件运行ldd时,它会告诉你程序使用的动态库和找到动态库的位置。但是如果你正在使用一个比较老
的glibc版本(2.2或更早),你可能会有一个有bug的ldd程序,它可能会报告在一个目录下发现一个动态库,但是真正运行程序时动态连接程序
(/lib/ld-linux.so.2)却可能到另外一个目录去找动态连接库。这通常因为/etc/ld.so.conf和
/etc/ld.so.cache文件不一致,或者/etc/ld.so.cache被破坏。在glibc
2.3.2版本上这个错误不会出现,可能ld-linux的这个bug已经被解决了。

尽管这样,ldd并不能把所有程序
依赖的动态库列出来,系统调用dlopen可以在需要的时候自动调入需要的动态库,而这些库可能不会被ldd列出来。作为glibc的一部分的NSS
(Name Server
Switch)库就是一个典型的例子,NSS的一个作用就是告诉应用程序到哪里去寻找系统帐号数据库。应用程序不会直接连接到NSS库,glibc则会通
过dlopen自动调入NSS库。如果这样的库偶然丢失,你不会被告知存在库依赖问题,但这样的程序就无法通过用户名解析得到用户ID了。让我们看一个例
子:

whoami程序会给出你自己的用户名,这个程序在一些需要知道运行程序的真正用户的脚本程序里面非常有用,whoami的一个示例输出如下:
代码:

# whoami
root
  


假设因为某种原因在升级glibc的过程中负责用户名和用户ID转换的库NSS丢失,我们可以通过把nss库改名来模拟这个环境:
代码:

# mv /lib/libnss_files.so.2 /lib/libnss_files.so.2.backup  
# whoami
whoami: cannot find username for UID 0  
  


这里你可以看到,运行whoami时出现了错误,ldd程序的输出不会提供有用的帮助:
代码:

# ldd /usr/bin/whoami
libc.so.6 => /lib/libc.so.6 (0x4001f000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
  


你只会看到whoami依赖Libc.so.6和ld-linux.so.2,它没有给出运行whoami所必须的其他库。这里时用strace跟踪whoami时的输出:
代码:

strace -o whoami-strace.txt whoami
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)  
open("/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/i686", 0xbffff190) = -1 ENOENT (No such file or directory)
open("/lib/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)  
open("/lib/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/lib", {st_mode=S_IFDIR|0755, st_size=2352, ...}) = 0
open("/usr/lib/i686/mmx/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
stat64("/usr/lib/i686/mmx", 0xbffff190) = -1 ENOENT (No such file or directory)  
open("/usr/lib/i686/libnss_files.so.2", O_RDONLY) = -1 ENOENT (No such file or directory)
  


你可以发现在不同目录下面查找libnss.so.2的尝试,但是都失败了。如果没有strace这样的工具,很难发现这个错误是由于缺少动态库造成的。现在只需要找到libnss.so.2并把它放回到正确的位置就可以了。

[BOLD]限制strace只跟踪特定的系统调用[/BOLD]
如果你已经知道你要找什么,你可以让strace只跟踪一些类型的系统调用。例如,你需要看看在configure脚本里面执行的程序,你需要监视的系统调用就是execve。让strace只记录execve的调用用这个命令:
代码:
strace -f -o configure-strace.txt -e execve ./configure
  


部分输出结果为:
代码:

2720 execve("/usr/bin/expr", ["expr", "a", ":", "(a)"], [/* 31 vars */]) = 0
2725 execve("/bin/basename", ["basename", "./configure"], [/* 31 vars */]) = 0
2726 execve("/bin/chmod", ["chmod", "+x", "conftest.sh"], [/* 31 vars */]) = 0
2729 execve("/bin/rm", ["rm", "-f", "conftest.sh"], [/* 31 vars */]) = 0
2731 execve("/usr/bin/expr", ["expr", "99", "+", "1"], [/* 31 vars */]) = 0
2736 execve("/bin/ln", ["ln", "-s", "conf2693.file", "conf2693"], [/* 31 vars */]) = 0
  



已经看到了,strace不仅可以被程序员使用,普通系统管理员和用户也可以使用strace来调试系统错误。必须承认,strace的输出不总是容易理
解,但是很多输出对大多数人来说是不重要的。你会慢慢学会从大量输出中找到你可能需要的信息,像权限错误,文件未找到之类的,那时strace就会成为一
个有力的工具了。

               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/35079/showart_274392.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP