linux下cp覆盖原so文件时引起的段错误原因确定
原创作品,转载请注明出处http://www.cnblogs.com/leo0000/p/5694416.html
最近因为一个很有意思的段错误学习了一些新的东西。
当时现象是这样的,程序正在运行,系统升级,此时某些so已经被该程序所使用,现在把这些so文件覆盖了,导致了该程序崩溃。
调试dump文件可以发现是崩溃在了ld解析函数符号的时候,然后查看libc的源码,发现崩溃的函数checkmatch传入的参数是空指针,所以导致了崩溃。因为受到以前写裸机代码的影响,裸机是这样的,如果前2M stepstorm不够用,那么在stepstorm中的代码就把nandflash中的代码拷贝到内存中,然后跳转到内存中去运行,所以此时就算源文件再怎么被修改也不会受到nandflash中的内容影响。下面先讲两个需要用到的知识点。
linux下很重要的一点是,一个文件可以被很多应用程序打开,同一时间的确只有一个应用程序可以对该文件读写,但是在不同时刻,所有应用程序对文件的操作都会影响到其他已打开该文件的应用程序,因为在每次读写前,系统调用read和write会对内存中的内容进行有效性判断。
再讲一个有关mmap或者mmap2的事情,
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
addr是要映射到的内存地址,返回值也是被映射到的内存地址,因为一般指定为0,有内核选择一段可用的内存空间。
len表示要映射的内存大小。
prot表示这段内存的访问权限。
flags表示映射后内存的类型,主要是对该内存的写是否会影响到原文件。
fd表示文件描述符,
offset表示需要映射的文件内容相对文件头偏移量。
映射完了之后,对这个内存的访问就是对文件的访问。
下面看栗子:
源码:
共享库:
#include <stdio.h> int fun1()
{
printf("fun1\n");
}
main函数
int fun1(); int main()
{
while(){
sleep();
fun1();
}
}
这个代码很简单,下面先用strace跟踪下test的运行:
strace ./test
execve("./test", ["./test"], [/* 22 vars */]) =
brk() = 0x9653000
access("/etc/ld.so.nohwcap", F_OK) = - ENOENT (No such file or directory)
mmap2(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, ) = 0xb7731000
access("/etc/ld.so.preload", R_OK) = - ENOENT (No such file or directory)
open("tls/i686/sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/i686/sse2/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/i686/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/i686/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/sse2/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/sse2/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("sse2/cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("sse2/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("cmov/lib1.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("lib1.so", O_RDONLY|O_CLOEXEC) =
read(, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260\3\0\0004\0\0\0"..., ) =
fstat64(, {st_mode=S_IFREG|, st_size=, ...}) =
getcwd("/home/keda/caozhenhua/test/updateso", ) =
mmap2(NULL, , PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, , ) = 0xb772e000
mmap2(0xb772f000, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, , ) = 0xb772f000
close() =
open("tls/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("tls/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("i686/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/i686/sse2/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/i686/sse2", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/i686/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/i686", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/sse2/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/sse2", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/tls", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/i686/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/i686/sse2/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/i686/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/i686/sse2", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/i686/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/i686/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/i686/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/i686", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/sse2/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/sse2/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/sse2/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/sse2", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/cmov/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso/cmov", 0xbf9bd320) = - ENOENT (No such file or directory)
open("/home/keda/caozhenhua/test/updateso/libc.so.6", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
stat64("/home/keda/caozhenhua/test/updateso", {st_mode=S_IFDIR|, st_size=, ...}) =
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) =
fstat64(, {st_mode=S_IFREG|, st_size=, ...}) =
mmap2(NULL, , PROT_READ, MAP_PRIVATE, , ) = 0xb7719000
close() =
access("/etc/ld.so.nohwcap", F_OK) = - ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) =
read(, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., ) =
fstat64(, {st_mode=S_IFREG|, st_size=, ...}) =
mmap2(NULL, , PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, , ) = 0xb7574000
mmap2(0xb7713000, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, , 0x19f) = 0xb7713000
mmap2(0xb7716000, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -, ) = 0xb7716000
close() =
mmap2(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, ) = 0xb7573000
mmap2(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, ) = 0xb7572000
set_thread_area({entry_number:- -> , base_addr:0xb75726c0, limit:, seg_32bit:, contents:, read_exec_only:, limit_in_pages:, seg_not_present:, useable:}) =
mprotect(0xb7713000, , PROT_READ) =
mprotect(0xb772e000, , PROT_READ|PROT_WRITE) =
mprotect(0xb772e000, , PROT_READ|PROT_EXEC) =
mprotect(0xb772f000, , PROT_READ) =
mprotect(0x8049000, , PROT_READ) =
mprotect(0xb7754000, , PROT_READ) =
munmap(0xb7719000, ) =
rt_sigprocmask(SIG_BLOCK, [CHLD], [], ) =
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], }, ) =
rt_sigprocmask(SIG_SETMASK, [], NULL, ) =
nanosleep({, }, 0xbf9bd958) =
fstat64(, {st_mode=S_IFCHR|, st_rdev=makedev(, ), ...}) =
mmap2(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, ) = 0xb772d000
write(, "fun1\n", 5fun1
) =
rt_sigprocmask(SIG_BLOCK, [CHLD], [], ) =
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], }, ) =
rt_sigprocmask(SIG_SETMASK, [], NULL, ) =
nanosleep({, },
可以看到每打开一个共享库,linux利用的是mmap2,而不是像裸机一样的read和write。
所以这样的话,比如需要跳转到共享库中的某个函数,如果是第一次的话,那么需要从文件中把内容加载到内存,然后再运行。
那么如果在程序运行时,出现缺页,那么就需要从内存中重新读取该文件的该段内容,而如果该文件被修改了而且该段是第一次被访问,那么读取到的内容将会导致不可预知的错误。
接下来再对mmap实验。
源码:
#include <sys/mman.h>
#include <fcntl.h> int main()
{
int fd;
int i = ;
char *buf;
fd = open("./libvsipstack.a",O_RDONLY);
buf = mmap(,,PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE,fd,);
while()
{
i++;
sleep();
printf("%c\n",buf[i]);
}
}
mmap的映射方式和加载库的方式一致,
然后在程序运行时我用vi修改了libvsipstack.a,修改之后可以看到libvsipstack.a是有一个备份文件的。此时是可以保证数据是正确的。
-rwxrwxrwx root root -- : \
-rwxrwxrwx root root -- : a.out
-rwxrwxrwx root root -- : hello.c
-rwxrwxrwx root root -- : libvsipstack.a
-????????? ? ? ? ? ? libvsipstack.a~
-rwxrwxrwx root root -- : test.c
而当我们在上面的实验中,用libvsipstack.a覆盖lib1.so,并没有导致lib1.so有一个备份,而是变成了libvsipstack.a一样的文件。所以下一次读取将会和源文件不一致。
-rwxrwxrwx root root -- : core
-rwxrwxrwx root root -- : lib1.c
-rwxrwxrwx root root -- : lib1.so
-rwxrwxrwx root root -- : lib2.so
-rwxrwxrwx root root -- : libvsipstack.a
-rwxrwxrwx root root -- : test
-rwxrwxrwx root root -- : test.c
需要知道当源文件更新时,使用mmap到的内存,系统会去重新读取文件中的内容。
所以当库被更新时,同时会更新内存中的内容。
总结一下,
是否崩在ld中,是由是否是第一次调用该函数决定的,因为只有第一次调用才会需要去解析plt表中的内容。
首先内存的布局是根据elf文件头来部署的。所以替换前后的文件不是同一个那么就会导致非法指令之类的错误,根本无法确定错误来源。
但如果替换前后库是一致的,那么差别就在于,重定向表的内容变的无效了。
重定向表有两种一种是在程序启动时就完成符号解析的,看栗子
Dump of assembler code for function fun1:
0xb7fd846c <+>: push %ebp
0xb7fd846d <+>: e5 mov %esp,%ebp
0xb7fd846f <+>: ec sub $0x18,%esp
0xb7fd8472 <+>: c7 d4 movl $0x4d4,(%esp)
0xb7fd8479 <+>: e8 fc ff ff ff call 0xb7fd847a <fun1+>
0xb7fd847e <+>: c9 leave
0xb7fd847f <+>: c3 ret
本来+13位置处的代码已经完成了重定向,但是重新读取之后呢,恢复了重定向之前的情况,结果崩溃。
另一种是懒惰式的加载,是先跳到plt表的情况,看栗子
0xb7fd845c <+>: push %ebp
0xb7fd845d <+>: mov %esp,%ebp
0xb7fd845f <+>: push %ebx
0xb7fd8460 <+>: sub $0x14,%esp
0xb7fd8463 <+>: call 0xb7fd8457 <__i686.get_pc_thunk.bx>
0xb7fd8468 <+>: add $0x1b8c,%ebx
0xb7fd846e <+>: lea -0x1b12(%ebx),%eax
0xb7fd8474 <+>: mov %eax,(%esp)
0xb7fd8477 <+>: call 0xb7fd8380 <puts@plt>
0xb7fd847c <+>: add $0x14,%esp
0xb7fd847f <+>: pop %ebx
0xb7fd8480 <+>: pop %ebp
0xb7fd8481 <+>: ret
End of assembler dump.
(gdb) disassemble 0xb7fd8380
Dump of assembler code for function puts@plt:
0xb7fd8380 <+>: jmp *0x10(%ebx)
0xb7fd8386 <+>: push $0x8
0xb7fd838b <+>: jmp 0xb7fd8360
End of assembler dump.
(gdb) i r
eax 0xb7fd84e2 -
ecx 0xbffff6a8 -
edx 0xb7fbeff4 -
ebx 0xb7fd9ff4 -
esp 0xbffff6ac 0xbffff6ac
ebp 0xbffff6c8 0xbffff6c8
esi 0x0
edi 0x0
eip 0x386 0x386
eflags 0x210292 [ AF SF IF RF ID ]
cs 0x73
ss 0x7b
ds 0x7b
es 0x7b
fs 0x0
gs 0x33
(gdb) disassemble 0x00000386
No function contains specified address.
(gdb) x/x 0xb7fd9ff4+0x10
0xb7fda004: 0x00000386
可以看到先跳到了plt表,因为是重新读入,相当于还没有重定位,所以跳转到下一句话,可以反汇编看到0x386就是原反汇编没有偏移地址的地址大小。
<puts@plt>:
: ff a3 jmp *0x10(%ebx)
: push $0x8
38b: e9 d0 ff ff ff jmp <_init+0x3c>
所以这里面的情况相当于基地址被初始为了0,导致崩溃。
因为mmap被调用时指明的是private,所以内存的修改不会影响到文件的内容,但是文件的修改会影响到内存的内容,所以导致了崩溃的产生。
说这个是系统的bug也可以,说是自己的使用不当也可以。
还有一个是权限问题,除了拥有root权限的用户,是不可以这样随意覆盖文件的。
linux下cp覆盖原so文件时引起的段错误原因确定的更多相关文章
- linux下使用gcc/g++编译代码时gets函数有错误
今天在linux中使用个g++编译一个名为myfirst.cpp的代码的时候,出现如下错误 myfirst.cpp: In function ‘int main()’:myfirst.cpp:11:2 ...
- 《Linux下cp XXX1 XXX2的功能》的实现
<Linux下cp XXX1 XXX2的功能>的实现 一.题目要求 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP支持两个参数: java MyC ...
- 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能
题目:编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能 要求:MyCP支持两个参数: java MyCP -tx XXX1.txt XXX2.bin 用来把文本文件(内容为十 ...
- 20175303 Mycp实现Linux下cp xxx1 xxx2的功能
20175303 Mycp实现Linux下cp xxx1 xxx2的功能 一.题目要求 编写MyCP2.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP2支持两个参数: ja ...
- 学号20175313 《实现Linux下cp XXX1 XXX2的功能(二)》第九周
目录 MyCP2 一.题目要求 二.题目理解 三.需求分析 四.设计思路 五.伪代码分析 六.代码链接 七.代码实现过程中遇到的问题 八.运行结果截图 九.心得体会 十.参考资料 MyCP2 一.题目 ...
- 学号20175313 《实现Linux下cp XXX1 XXX2的功能(一)》第九周
目录 MyCP 一.题目要求 二.题目理解 三.需求分析 四.设计思路 五.伪代码分析 六.代码链接 七.代码实现过程中遇到的问题 八.运行结果截图 九.参考资料 MyCP 一.题目要求 编写MyCP ...
- 编程实现类似Linux下cp功能
MyCP的代码实现 一.题目要求: 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP支持两个参数: java MyCP -tx XXX1.txt XXX2.bi ...
- linux下shell脚本执行jar文件
最近在搞一个shell脚本启动jar文件个关闭jar文件的东东.搞得我都蛋疼了.今天晚上终于弄好了 话说,小弟的linux只是刚入门,经过各方查资料终于搞定了.话不多说,下面开始上小弟写的shell脚 ...
- linux 下C语言编程库文件处理与Makefile编写
做开发快3年了,在linux下编译安装软件算是家常便饭了.就拿gcc来说,都有不下10次了,可基本每次都会碰到些奇奇怪怪的问题.看来还是像vs.codeblocks这样的ide把人弄蠢了.便下定决心一 ...
随机推荐
- XML通过XSL格式化的那点事(XML到自定义节点折叠显示)
引言 有时我们想看下系统生成的XML文件(如XML格式的Project文件),如果文件结构简单,我们浏览器看起来还比较方便,但是随着XML schema复杂后就变得让人头疼啦,单独写一个程序去做展现又 ...
- Serial Port Programming using Win32 API(转载)
In this tutorial we will learn How to communicate with an external device like a microcontroller boa ...
- Qt5.7.0配置选项(configure非常详细的参数)
configure是一个命令行工具,用于配置Qt编译到指定平台.configure必须运行于Qt源码根目录.当运行configure时,编译源码使用的是所选工具链中的make工具. 一.源码目录.编译 ...
- SBT的用法
最近需要用scala写一个测试框架,主要看中了它比较强大的DSL抽象能力和有一个简洁的web框架play,还有比较好的性能测试框架gatling和测试框架Scalatest和Spec2.揉在一起会有啥 ...
- VS2012的调试插件Image Watch,opencv编程神器
今天配置 opencv3.0 时无意中看到 Image Watch 这样一个VS2012的调试插件,适用了下,特别好用. 部分链接: Image Watch 的 下载链接 OpenCV关于ImageW ...
- 【分享】国外后台界面HTML源码 [免费]
国外后台界面HTML模版下载,里面的文字是英文的,不过可以修改成中文,带有数据统计界面和曲线图,本套模板相对完整,在主界面上点击那些菜单,都可以点开二级页面,希望对搞代码的程序员,对不擅长美工的朋友提 ...
- .Net配置中心-服务端/客户端
服务端 管理应用,一个应用对应一个站点. 管理应用下的配置. 在保存配置的时候,会更新应用的版本号. 客服端 其他站点引用DLL,并在Global的App_Start中调用ConfigCenter的I ...
- 使用对话框 —— Dialog
对话框就是一般的弹出窗口,主要用来提示用户,和用户交互. 创建Activity对话框 使用Activity模拟对话框.这个比较简单,主要是使用Activity自带的Dialog主题. 创建 ...
- 【抓包工具】wireshark
wireshark下载地址:http://download.csdn.net/detail/victoria_vicky/8819777 一.wireshark优劣势 wireshark劣势:只能查看 ...
- logging 模块误用导致的内存泄露
首先介绍下怎么发现的吧, 线上的项目日志是通过 logging 模块打到 syslog 里, 跑了一段时间后发现 syslog 的 UDP 连接超过了 8W, 没错是 8 W. 主要是 logging ...