dutor.net
域名年龄: 16年26天HTTP/1.1 200 OK 服务器:nginx/1.4.1 访问时间:2013年10月17日 07:37:36 类型:text/html; charset=UTF-8 Transfer-Encoding: chunked 连接:keep-alive 语言环境:PHP/5.3.6-13ubuntu3.1 X-Pingback: http://www.dutor.net/xmlrpc.php Content-Encoding: gzip 网站编码:UTF-8
DutorHomeGuestBookCollectionsSubscribeaccept『死循环』 场景:一个多线程服务器,每个线程执行一个事件循环。在事件循环开始前,调用 socket/bind/listen 监听端口,然后将监听句柄(fd)添加到 epoll,然后开始事件循环,执行 epoll_wait。epoll_wait 返回有效事件时,对于监听事件,调用 accept 建立新连接,将该连接句柄添加到 epoll;对于普通连接,调用 read/write 进行网络 IO 及其他处理逻辑。 现象:服务器进程 CPU 占用彪高,几乎每个事件循环都在 accept,客户端出现超时。 原因:ulimit -n 为 65535, 进程打开的 fd 已经超过该数值,导致 accept 时无法取得 fd 而失败,而此时 TCP 连接的三次握手已经建立。又因为 epoll 使用的 LT 触发模式,该连接事件会不停地由 epoll 上报,于是产生了所谓的『死循环』,其实是事件循环闲不下来了,即使没有实际的 网络 IO。Tags: epoll,networks.Filed under: Unix/Linux By dutor @ October 8th, 2013, 68 viewsComments (0)记一次栈缓冲区溢出的调试 is_alive 实现了一个超时时间可控的 connect。如果你现在已经明白怎么回事了,请务必留下您的大名,我可是花了两周零五分钟才看出来的。 对,就是万恶的 select,FD_SET 可能越界,因为 fd_set 中只有 FD_SETSIZE 个 bit 来标识 fd。FD_SETSIZE 是一个宏,定义在 /usr/include/sys/select.h。查看该文件,FD_SET 并未对 fd 做参数检查,因此当 fd 大于 1024 时,FD_SET 就写了它不该写的地方了,写到谁,有没有影响,有多大影响,什么时候触发,程序会不会 core 掉,什么时候 core 掉,这些都要看造化了。比如这次是写到栈里的 return address 了,导致程序跑飞。另外一个 core dump 里面,某个类的 this 从 0x1e41500 变成了 0x11e41500,当时定位不出写越界,只好认为世界末日前宇宙射线爆发导致硬件异常,聊以自慰。 吐个槽,Linux Kernel 就不能增加一个带超时的 connect 调用?glibc 里面,FD_SETSIZE 只用在 fd_set 里的位域定义和 FD_ZERO,FD_SET 没有做任何检查;Kernel 里面的 select 实现,申请内核态 fd_set 的时候,完全依据 fd 的大小,并没有大小的限制(如果我没有漏掉某些逻辑的话)。 总结: 如果出现 stack corruption,几乎一定是栈的缓冲区写越界了;基本不可能是用户态的堆内存越界,因为除了主线程,每个线程的栈空间两侧又有一个 page 的虚拟空间做 gap,这些 gap,不可读,不可写,不可执行。主线程的 stack 是按需向下生长的,在 IA64 环境下,也不可能和 mmap 区域无缝相邻。当然,跳着写堆内存,或者偏移量算错就另说了。 这篇文章中 FD_SET 写越界只篡改了一个 bit,而且被篡改的那个 bit 可能本来就是 1,非常难重现,而且 stack 的破坏程度较轻,通过查看栈内容,可以大致知道函数的调用关系。如果是类似上文 foo() 中的 memset 越界,栈可能真的面目全非了,但这时候栈内容通常是有规律可循的。 根据栈内容判断函数调用关系,需要知道 call 指令的语义,栈中保存的是 caller 中 call 指令的下一条指令,这条指令的前一条才是 callee。Tags: GDB,汇编.Filed under: ToolKits,边走编程 By dutor @ September 23rd, 2013, 281 viewsComments (0)GDB 『高级』命令set follow-fork-mode child,被调试进程执行 fork 时,自动 attach;set scheduler-locking on,调试时,禁用线程切换,可选 on/off/step,默认 off;symbol-file target.debug,添加独立的 debuginfo 文件;i sharedlibrary,查看共享文件映射信息;add-symbol-file libxx.debug ADDRESS,添加共享文件的 debuginfo 文件,ADDRESS 是共享文件的映射始址,由 i sharedlibrary 获得;gcc test.cpp -g -g3,调试信息中保留 MACRO;set logging on,GDB 的所有输入/输出都会被写入当前目录下的 gdb.txt;set print pretty on,打印对象,尤其是结构体时,格式更加友好;p $rip,打印 rip 寄存器;i reg,查看寄存器集,i registers-all 显示全部寄存器;display/i $rip,每次断点,打印下一条指令; l *0×608048,显示某指令地址对应的代码行,可执行文件包含调试信息时,亦可用 addr2line;x/40a $rsp,以地址形式打印 stack,栈乱掉时可救命;return 0,停止调试当前函数,并以指定值返回;p {tair::StorageManager}0×608048,将指定地址以某类型打印;p *array@10,打印数组 array 的前十个元素;gcore,将被调试进程 core dump,gcore 还是一个独立的命令,随 GDB 发布。Tags: GDB.Filed under: ToolKits By dutor @ September 23rd, 2013, 63 viewsComments (0)g++ 函数域静态变量的初始化 分析上述汇编代码。首先获取 guard 变量,判断低字节是否为 0,若非零,表示已经初始化,可以直接使用。否则,将 guard 作为参数调用 __cxa_guard_acquire,如果锁成功,调用 init() 初始化静态变量 foo()::n,然后释放锁。如果锁失败,说明产生竞态条件,则会阻塞当前线程,不同于普通锁的地方在于,__cxa_guard_acquire 是有返回值的(当然 pthread_lock 也有返回值,但用途不同),如果发生了等待,__cxa_guard_acquire 返回 0,并不会进入 foo()::n 的初始化过程(其他线程已经初始化过了,初始化失败的情况就不细究了)。 为了验证上述分析,可以将 init() 实现成一个耗时的操作,令多个线程“同时”调用 foo(),然后查看各个线程的运行状态。 对于单线程程序,静态变量的保护是没有必要的,g++ 的 -fno-threadsafe-statics 选项可以禁掉该机制。Tags: 汇编,编译器.Filed under: 之语言特
© 2010 - 2020 网站综合信息查询 同IP网站查询 相关类似网站查询 网站备案查询网站地图 最新查询 最近更新 优秀网站 热门网站 全部网站 同IP查询 备案查询
2025-05-10 06:09, Process in 0.0067 second.