- 论坛徽章:
- 3
|
本帖最后由 compare2000 于 2013-12-30 10:43 编辑
11.3 利用dropwatch进行丢包故障定位
Linux自2.6.30开始具有Network Drop Monitor功能;协议栈对于非法数据包会进行丢弃,即便是合法数据包,若超过接收缓存大小也会进行丢弃;但是这些丢弃的数据包无法通过内核日志进行观看;但若内核支持Network Drop Monitor,则可以使用dropwatch进行查看。
Dropwatch就是各种协议释放socket缓冲区时(kfree_skb函数)收集信息。可以应对IP、ARP、ICMP、IGMP、UDP协议的数据包。
因此,即使不使用proc文件系统或者工具针对各个协议收集信息,也可以通过dorpwatch将数据集中到一起进行确认。不需要为了调查潜在的网络性能而改变已有的应用程序。
11.3.1 软件使用
编译内核时,需要启动Network packet drop alerting service(NET_DROP_MONITOR)并重新编译内核。
执行dropwatch命令,显示dropwatch和提示符后输入start即开始检测丢包情况
Initalizing null lookup method
dropwatch > start
在检测的过程中,如果输入Ctrl+C则返回提示符命令行,输入stop或者exit后就可以结束。
例如:
1 drops at location 0xffffffff8887453
14 drops at location 0xffffffff88964322
17 drops at location 0xffffffff8745466
1 drops at location 0xffffffff8867311
此时检测到丢包后,会打印出代码段对应丢包代码的位置,但是不是很直观,不能看出具体是哪一段代码出现了丢包。此时可以借助-l命令选项,即使用/proc/kallsyms将具体的地址转换为函数名。
dropwatch –l kas
其中kas是kernel all symbols的缩写。
例如:
1 drops at netlink_unicast+249 表示在netlink_unicast偏移249处丢包1个
6 drops at __udp4_lib_mcast_deliver+364
13 drops at __udp4_lib_mcast_deliver+364
1 drops at __netif_receive_skb+48a
11 drops at __udp4_lib_mcast_deliver+364
7 drops at __udp4_lib_mcast_deliver+364
11.4 TCP/UDP故障定位
11.4.1 现象描述
某业务环境,升级版本后TCP无法建立连接,业务侧统计失败率为100%,无法满足基本通信要求。
11.4.2 定位过程
1. 使用tcpdump抓包,发现接收端抓到的报文L4校验和错误,导致接收端直接将报文丢弃;
2. 抓取多组报文观察并编制程序计算校验和,发现这些校验和存在一定的规律,一组为报文中的校验和为报文伪头部的校验和,另一组报文中的校验和与正确的校验和差值为伪头部的校验和。
3. 与业务一起分析其相关代码,观察到其强制设置报文的skb->ip_summed为硬件计算校验和方式。但是对于其发送的报文,分为转发和自己组包两类。对于转发,除了修改skb->ip_summed字段,其他并无变动;对于自己组包的报文,除了修改skb->ip_summed字段为硬件计算校验和方式,同时将tcphdr->check字段赋为0。
4. 分析网卡驱动,当网卡驱动检测到skb->ip_summed为硬件计算校验和时,则告诉硬件需要计算L4校验和,然后由硬件发送报文。
5. 分析内核,当硬件具备计算L4校验和能力,并且报文的skb->ip_summed设置为硬件计算校验和,则协议栈计算伪头部的校验和并填充到tcphdr->check字段,据此可以推断出网卡硬件计算校验和时,默认情况下是需要tcphdr->check字段已经填充为伪头部校验和。而第3步中提到,业务两种组包场景下,显然都不满足这个要求。
6. 业务进行修改自己的协议栈,保持与内核一致的处理方式。再测试后现象消失。
11.4.3 Linux内核协议栈TCP校验和计算流程分析
Linux内核协议栈中TCP校验和计算流程分析
1. 无论是首次发送TCP段,还是重传,或者是建立TCP连接时发送SYN段,都会调用到tcp_transmit_skb()。该函数的注释也可以看出这点。
2.该函数处理之前,报文中还没有TCP头,所以需要先填充TCP头,然后计算TCP校验和。
1)构建TCP头
2)调用对应的钩子函数计算TCP校验和
对于IPv4-TCP报文来说,该Hook实际上为tcp_v4_send_check()函数。
3)tcp_v4_send_check()计算TCP校验和
从该函数的实现来看,如果skb中设置了让硬件计算校验和,则只计算伪头部的校验和并填充到th->check中,否则计算伪头部及TCP层整体校验和。
3.若组包时让硬件计算校验和,但硬件不具备该能力Linux协议栈处理方式
从第2步可以看出来,在报文送到dev_queue_xmit()之前,此种场景下th->check中已经有了伪头部的校验和。
dev_queue_xmit()中的判断,如果硬件不具备计算校验和的能力,则调用skb_checksum_help()计算TCP层的校验和,这样保证了该场景下L4层校验和的正确性。
12 追踪
12.1 Strace/lstrace进行追踪
strace、ltrace是linux下的跟踪工具,strace用来跟踪一个进程的系统调用或信号产生的情况,而 ltrace用来跟踪进程调用库函数的情况。
它们不仅可以从命令行调试一个新开始的程序,也可以把strace或ltrace绑定到一个已有的PID上来调试一个正在运行的程序。主要能跟踪进程运行过程中调用的系统函数/库函数,还有时间戳等信息。它们的用法都很类似,下面是strace/ltrace常见的参数:
参数 含义
-f 除了跟踪当前进程外,还跟踪其子进程
-o file 将输出信息写到文件file中,而不是显示到标准错误输出(stderr)
-p pid 绑定到一个由pid对应的正在运行的进程。此参数常用来调试后台进程
-T 每个库函数/系统函数调用的执行时间
下面分别举例看如何使用。
12.1.1 strace跟踪系统调用
1、 启动跟踪,跟踪ls –al命令的系统调用,结果写入strace_ls.txt文件
#strace -o /mbsc/log/strace_ls.txt ls -al
2、 查看跟踪的情况(有截断):#cat /mbsc/log/strace_ls.txt
该信息解释如下:
系统函数(调用参数1,调用参数2,….) = 系统调用返回值
3、 跟踪系统调用的耗时:
#strace -o /mbsc/log/strace_ls.txt -T ls -al
4、 strace的详细参数(查看帮助信息):
12.1.2 ltrace跟踪库调用
5、 启动跟踪ltrace ls -al : 查看ls –al命令调用的库函数
打印格式:
库函数名(参数1,参数2,…) = 返回值
6、 同时跟踪库函数及系统调用:ltrace -S ls -al (结果有截断)
说明:以SYS_开头的就是系统调用。
7、 跟踪调用耗时:ltrace -S -T ls -al
说明:最右侧就是该函数执行时间
8、 ltrace函数详细参数(查看帮助)
下面该得文档给出了更为详细的使用说明
Linux问题定位手段之strace使用详解
strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用。 strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息,而且不需要以任何特殊的方式来构建内核。
一、参数详解:
dl_scu ~ # strace
usage: strace [-dffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]
[-p pid] ... [-s strsize] [-u username] [-E var=val] ...
[command [arg ...]]
or: strace -c -D [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...
[command [arg ...]]
-c -- count time, calls, and errors for each syscall and report summary
-f -- follow forks, -ff -- with output into separate files
-F -- attempt to follow vforks, -h -- print help message
-i -- print instruction pointer at time of syscall
-q -- suppress messages about attaching, detaching, etc.
-r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs
-T -- print time spent in each syscall, -V -- print version
-v -- verbose mode: print unabbreviated argv, stat, termio, etc. args
-x -- print non-ascii strings in hex, -xx -- print all strings in hex
-a column -- alignment COLUMN for printing syscall results (default 40)
-e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...
options: trace, abbrev, verbose, raw, signal, read, or write
-o file -- send trace output to FILE instead of stderr
-O overhead -- set overhead for tracing syscalls to OVERHEAD usecs
-p pid -- trace process with process id PID, may be repeated
-D -- run tracer process as a detached grandchild, not as parent
-s strsize -- limit length of print strings to STRSIZE chars (default 32)
-S sortby -- sort syscall counts by: time, calls, name, nothing (default time)
-u username -- run command as username handling setuid and/or setgid
-E var=val -- put var=val in the environment for command
-E var -- remove var from the environment for command
??
主要参数说明:
?? -e expr
?? A qualifying expression which modifies which events to trace or how to trace
?? them. The format of the expression is:
?? [qualifier=][!]value1[,value2]...
?? 这里qualifier可以是trace、abbrev、verbose、raw、signal、read或者write。
?? value是qualifier相关的符号或数值。缺省qualifier是trace。!表示取反。
?? -eopen等价于-e trace=open,表示只跟踪open系统调用。-etrace=!open意思是跟踪除open系统调用之外的其他所有系统调用。此外value还可以取值all和none。
如:strace -o temp1.txt -eopen cat temp.txt
?? strace -o temp2.txt -e trace=\!open cat temp.txt或
strace -o temp4.txt -etrace='!'open cat temp.txt
?? 注意:某些shell用!表示重复历史指令,此时可能需要引号、转义符号(\)的帮助。
?? -e execve 只记录 execve 这类系统调用。
如:strace -e execve ls
?? -e trace=set
?? 只跟踪指定的系统调用列表。决定跟踪哪些系统调用时,-c选项很有用。
?? trace=open,close,read,write意即只跟踪这四种系统调用,缺省是trace=all
?? 如:strace -o temp5.txt -etrace=open,close,read,write cat temp.txt
?? -e trace=process:跟踪所有与进程管理相关的系统调用
?? Trace all system calls which involve process management. This is
?? useful for watching the fork, wait, and exec steps of a process.
?? 如:strace -o temp7.txt -e trace=process cat temp.txt
?? -e trace=network
?? 跟踪所有和网络相关的系统调用
? 如:strace -e trace=network ping 172.14.107.101
?? -e trace=signal
跟踪所有与信号相关的系统调用
?? -e trace=ipc
??跟踪所有与IPC相关的系统调用
-e trace=file
只跟踪有关文件操作的系统调用.
如:strace -o file.trace -e trace=file cat temp.txt
?? -e abbrev=set
?? Abbreviate the output from printing each member of large structures.
?? 缺省是abbrev=all,-v选项等价于abbrev=none
??
?? -e signal=set
?? 只跟踪指定的信号列表,缺省是signal=all。signal=!SIGIO (or signal=!io)
?? 导致 SIGIO 信号不被跟踪
??
-e read=set
输出从指定文件中读出的数据.
例如: -e read=3,5
-e write=set
输出写入到指定文件中的数据.
-e raw=set
将指定的系统调用的参数以十六进制显示.
?? -f
??跟踪某个进程时,如果发生fork()调用,则选择跟踪子进程
??
?? -ff
?? 如果-o file选项有效指定,则跟踪过程中新产生的其他相关进程的信息分别写
?? 入file.pid,这里pid是各个进程号。
?? 如:strace -o temp1 -ff cat temp.txt
?? -i
?? 输出系统调用的入口指针
? 如:strace -o tmp.txt -i cat temp.txt
?? -o filename
?? 指定保存strace输出信息的文件,默认使用标准错误输出stderr
??
?? -p pid
?? 指定待跟踪的进程号,可以用Ctrl+C终止这种跟踪而被跟踪进程继续运行。可以
?? 指定多达32个-p参数同时进行跟踪,此参数常用来调试后台进程。
?? 比如 strace -ff -o output -p 14653 -p 14117
-r
以第一个系统调用(通常为execve)计时,精确到微秒
?? 如:strace -o temp3.txt -r cat temp.txt
?? -t
?? 与-r选项类似,只不过-r采用相对时间戳,-t采用绝对时间戳(当前时钟)
?? 如:strace -o temp4.txt -t cat temp.txt
?? -tt
?? 与-t类似,绝对时间戳中包含微秒
如:strace -tt cat temp.txt
?? -T
?? 这个选项显示单个系统调用耗时
如:strace -T cat temp.txt
?? -u username
?? 用指定用户的UID、GID以及辅助组身份运行待跟踪程序
??
-x
以十六进制形式输出非标准字符串
-xx
所有字符串以十六进制形式输出
-q
禁止输出关于脱离的消息
-S sortby:分类排序
sort syscall counts by: time, calls, name, nothing (default time)
sortby取值:time、calls、name或nothing。
如按time排序:strace -c -D -S time cat temp.txt
-v
输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.如:strace -v -o file.txt cat tmp.txt
-c
统计每一系统调用所执行的时间,次数和出错的次数等
-d
输出strace关于标准错误的调试信息
-a column
?? 指定显示返回值的列位置,默认是40(从0开始计数),就是说"="出现在40列的位置。
-s strsize
?? 指定字符串最大显示长度,默认32。但文件名总是显示完整。
-h
?? 显示帮助信息
?? -V
?? 显示strace版本信息
目前我所使用的strace版本信息如下:
?? dl_scu ~ # strace -V
strace -- version 4.5.19
#strace -c -D ls
#strace -c -D -e trace=open ls
二、strace分析和定位问题案例:
引言
“Oops,系统挂死了..."
“Oops,程序崩溃了..."
“Oops,命令执行报错..."
对于维护人员来说,这样的悲剧每天都在上演。理想情况下,系统或应用程序的错误日志提供了足够全面的信息,通过查看相关日志,维护人员就能很快地定位出问 题发生的原因。但现实情况,许多错误日志打印模凌两可,更多地描述了出错时的现象(比如"could not open file","connect to XXX time out" ,而非出错的原因。
错误日志不能满足定位问题的需求,我们能从更“深层”的方面着手分析吗?程序或命令的执行,需要通过系统调用(system call)与操作系统产生交互,其实我们可以通过观察这些系统调用及其参数、返回值,界定出错的范围,甚至找出问题出现的根因。
在Linux中,strace就是这样一款工具。通过它,我们可以跟踪程序执行过程中产生的系统调用及接收到的信号,帮助我们分析程序或命令执行中遇到的异常情况。
一个简单的例子
如何使用strace对程序进行跟踪,如何查看相应的输出?下面我们通过一个例子来说明。
1.被跟踪程序示例
//main.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main( )
{
int fd ;
int i = 0 ;
fd = open( “/tmp/foo”, O_RDONLY ) ;
if ( fd < 0 )
i=5;
else
i=2;
return i;
}
以上程序尝试以只读的方式打开/tmp/foo文件,然后退出,其中只使用了open这一个系统调用函数。之后我们对该程序进行编译,生成可执行文件:
lx@LX:~$ gcc main.c -o main
2.strace跟踪输出
使用以下命令,我们将使用strace对以上程序进行跟踪,并将结果重定向至main.strace文件:
lx@LX:~$ strace -o main.strace ./main
接下来我们来看main.strace文件的内容:
lx@LX:~$ cat main.strace
1 execve("./main", ["./main"], [/* 43 vars */]) = 0
2 brk(0) = 0x9ac4000
3 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
4 mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7739000
5 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
6 open("/etc/ld.so.cache", O_RDONLY) = 3
7 fstat64(3, {st_mode=S_IFREG|0644, st_size=80682, ...}) = 0
8 mmap2(NULL, 80682, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7725000
9 close(3) = 0
10 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
11 open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
12 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
13 fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0
14 mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x56d000
15 mprotect(0x6c7000, 4096, PROT_NONE) = 0
16 mmap2(0x6c8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x6c8000
17 mmap2(0x6cb000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6cb000
18 close(3) = 0
19 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7724000
20 set_thread_area({entry_number:-1 -> 6, base_addr:0xb77248d0, limit:1048575, seg_32bit:1, contents:0, read_exec_ only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
21 mprotect(0x6c8000, 8192, PROT_READ) = 0
22 mprotect(0x8049000, 4096, PROT_READ) = 0
23 mprotect(0x4b0000, 4096, PROT_READ) = 0
24 munmap(0xb7725000, 80682) = 0
25 open("/tmp/foo", O_RDONLY) = -1 ENOENT (No such file or directory)
26 exit_group(5) = ?
看到这一堆输出,是否心生畏难情绪?不用担心,下面我们对输出逐条进行分析。
strace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:
系统调用的名称( 参数... ) = 返回值 错误标志和描述
Line 1: 对于命令行下执行的程序,execve(或exec系列调用中的某一个)均为strace输出系统调用中的第一个。strace首先调用fork或clone函数新建一个子进程,然后在子进程中调用exec载入需要执行的程序(这里为./main)
Line 2: 以0作为参数调用brk,返回值为内存管理的起始地址(若在子进程中调用malloc,则从0x9ac4000地址开始分配空间)
Line 3: 调用access函数检验/etc/ld.so.nohwcap是否存在
Line 4: 使用mmap2函数进行匿名内存映射,以此来获取8192bytes内存空间,该空间起始地址为0xb7739000,关于匿名内存映射,可以看这里
Line 6: 调用open函数尝试打开/etc/ld.so.cache文件,返回文件描述符为3
Line 7: fstat64函数获取/etc/ld.so.cache文件信息
Line 8: 调用mmap2函数将/etc/ld.so.cache文件映射至内存,关于使用mmap映射文件至内存,可以看这里
Line 9: close关闭文件描述符为3指向的/etc/ld.so.cache文件
Line12: 调用read,从/lib/i386-linux-gnu/libc.so.6该libc库文件中读取512bytes,即读取ELF头信息
Line15: 使用mprotect函数对0x6c7000起始的4096bytes空间进行保护(PROT_NONE表示不能访问,PROT_READ表示可以读取)
Line24: 调用munmap函数,将/etc/ld.so.cache文件从内存中去映射,与Line 8的mmap2对应
Line25: 对应源码中使用到的唯一的系统调用——open函数,使用其打开/tmp/foo文件
Line26: 子进程结束,退出码为5(为什么退出值为5?返回前面程序示例部分看看源码吧:)
3.输出分析
呼呼!看完这么多系统调用函数,是不是有点摸不着北?让我们从整体入手,回到主题strace上来。
从上面输出可以发现,真正能与源码对应上的只有open这一个系统调用(Line25),其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入libc函数库、设置内存映射等。
源码中的if语句或其他代码在相应strace输出中并没有体现,因为它们并没有唤起系统调用。strace只关心程序与系统之间产生的交互,因而strace不适用于程序逻辑代码的排错和分析。
使用strace处理程序挂死
最后我们通过一个程序示例,学习使用strace分析程序挂死的方法。
1.挂死程序源码
//hang.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
getpid(); //该系统调用起到标识作用
if(argc < 2)
{
printf("hang (user|system)\n" ;
return 1;
}
if(!strcmp(argv[1], "user" )
while(1);
else if(!strcmp(argv[1], "system" )
sleep(500);
return 0;
}
可向该程序传送user和system参数,以上代码使用死循环模拟用户态挂死,调用sleep模拟内核态程序挂死。
2.strace跟踪输出
用户态挂死跟踪输出:
lx@LX:~$ gcc hang.c -o hang
lx@LX:~$ strace ./hang user
……
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0xb59000, 4096, PROT_READ) = 0
munmap(0xb77bf000, 80682) = 0
getpid() = 14539
内核态挂死跟踪输出:
lx@LX:~$ strace ./hang system
……
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0xddf000, 4096, PROT_READ) = 0
munmap(0xb7855000, 80682) = 0
getpid() = 14543
rt_sigprocmask(SIG_BLOCK, [CHLD], [], = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, = 0
nanosleep({500, 0},
3.输出分析
用户态挂死情况下,strace在getpid()一行输出之后没有其他系统调用输出;进程在内核态挂死,最后一行的系统调用nanosleep不能完整显示,这里nanosleep没有返回值表示该调用尚未完成。
因而我们可以得出以下结论:使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。
当程序挂死在系统调用处,我们可以查看相应系统调用的man手册,了解在什么情况下该系统调用会出现挂死情况。另外,系统调用的参数也为我们提供了一些信息,例如挂死在如下系统调用:
read(16,
那我们可以知道read函数正在对文件描述符为16的文件或socket进行读取,进一步地,我们可以使用lsof工具,获取对应于文件描述符为16的文件名、该文件被哪些进程占用等信息。
小结
本文对Linux中常用的问题诊断工具strace进行了介绍,通过程序示例,介绍了strace的使用方法、输出格式以及使用strace分析程序挂死问题的方法。
|
|