C++ CGI报“资源访问错误”问题分析
一线上CGI偶发性会报“资源访问错误”,经过分析得出是因为CgiHost没有读取到CGI的任务输出,即CGI运行完成后连HTTP头都没有一点输出。
然而实际上,不可能没有任何输出,因为CGI至少有无条件的HTTP头部分输出,因此问题是输出丢失了。CGI和CgiHost间是通过重定向CGI的标准输出到Unix套接字进行交互的,如果这个套接字坏了,或者CGI的标准输出关闭了,自然不会有任何输出。但经测试,如果是关闭了套接字,报的错误不一样,因此直接排除这个可能。
经调查,该CGI的输出使用是C++库的std::cout,不是printf这套C库I/O,初步推断是std::cout对象的状态值不是goodbit。不是goodbit主要分三种情况:
1) 人为置为badbit等;
2) 程序有越界造成状态为badbit或failbit等;
3) 有类似“char* str=NULL; std::cout << str”的调用出现,造成状态为badbit;
4) std::cout调用的basic_streambuf<typename _CharT, typename _Traits>::sputn返回的大小不是期望值,造成状态为failbit。
从了解来看,第三种和第四种情况概率高出许多:
#include <iosfwd> namespace std { template<typename _CharT, typename _Traits = char_traits<_CharT> > class basic_ostream; typedef basic_ostream<char> ostream; } |
#include <iostream> namespace std { extern istream cin; /// Linked to standard input extern ostream cout; /// Linked to standard output extern ostream cerr; /// Linked to standard error (unbuffered) extern ostream clog; /// Linked to standard error (buffered) } |
#include <ostream> namespace std { // Partial specializations template<class _Traits> inline basic_ostream<char, _Traits>& operator<<(basic_ostream<char, _Traits>& __out, const char* __s) // 一个全局函数 { if (!__s) // 如果“__s”此时是NULL __out.setstate(ios_base::badbit); // 其它一些情况,可查看源码文件ostream.tcc else __ostream_insert(__out, __s, static_cast<streamsize>(_Traits::length(__s))); return __out; } // __ostream_write是一个位于名字空间std中的全局函数 // __ostream_write被兄弟函数__ostream_insert调用, // 而__ostream_insert又被函数全局函数operator<<调用 template<typename _CharT, typename _Traits> inline void __ostream_write(basic_ostream<_CharT, _Traits>& __out, const _CharT* __s, streamsize __n) { typedef basic_ostream<_CharT, _Traits> __ostream_type; typedef typename __ostream_type::ios_base __ios_base; // 类basic_streambuf的成员函数sputn实际调用的是兄弟函数xsputn // 而xsputn是一个虚拟函数。 // 虚类basic_streambuf有两个具体的子类:stringbuf和filebuf, // 对std::cout而言,对应的是filebuf,xsputn底层调用的实际是write函数。 // 注: // file版的xsputn实现在文件fstream.tcc中, // string版的xsputn实现在文件streambuf.tcc中。 const streamsize __put = __out.rdbuf()->sputn(__s, __n); if (__put != __n) __out.setstate(__ios_base::badbit); } template<typename _CharT, typename _Traits> class basic_streambuf { // 虚拟基类 public: virtual streamsize xsputn(const char_type* __s, streamsize __n); }; class basic_stringbuf: public basic_streambuf; // 字符串子类 class basic_filebuf: public basic_streambuf; // 文件子类 } |
typedef _Ios_Iostate iostate; enum _Ios_Iostate { _S_goodbit = 0, _S_badbit = 1L << 0, // cout << (char*)NULL _S_eofbit = 1L << 1, _S_failbit = 1L << 2, // write(buf,n) < n _S_ios_iostate_end = 1L << 16 }; // 注: // ios_base是一个普通类,并不是模板类 class ios_base { Iostate _M_streambuf_state; basic_streambuf<_CharT, _Traits>* _M_streambuf; // 对于fstream实际为basic_filebuf }; template<typename _CharT, typename _Traits> class basic_ios : public ios_base { }; // 这里用到了virtual继承, // 因为子类basic_iostream会同时继承basic_istream和basic_ostream, // 出现共享basic_ios,所以需要使用virtual继承 template<typename _CharT, typename _Traits> class basic_ostream : virtual public basic_ios<_CharT, _Traits> { }; |
(gdb) ptype std::char_traits<char> type = struct std::char_traits<char> { public: static void assign(char_type &, const char_type &); static char_type * assign(char_type *, std::size_t, char_type); static bool eq(const char_type &, const char_type &); static bool lt(const char_type &, const char_type &); static int_type compare(const char_type *, const char_type *, std::size_t); static std::size_t length(const char_type *); static const char_type * find(const char_type *, std::size_t, const char_type &); static char_type * move(char_type *, const char_type *, std::size_t); static char_type * copy(char_type *, const char_type *, std::size_t); static char_type to_char_type(const int_type &); static int_type to_int_type(const char_type &); static bool eq_int_type(const int_type &, const int_type &); static int_type eof(void); static int_type not_eof(const int_type &); typedef char char_type; typedef int int_type; } |
尝试使用GDB实地考察,遗憾的是无法对std::cout进行Debug,所以只有直接修改代码线上验证。但如果有办法取得std::cout的地址,然后根据对象的内存布局,找到成员_M_streambuf_state的内存位置,也是可以查看和动态修改的。
(gdb) p std::cout No symbol "cout" in namespace "std". (gdb) p &std::cout No symbol "cout" in namespace "std". 找到std::cout在进程中的位置,以便找到其成员_M_streambuf_state在进程中的位置 (gdb) p _ZSt4cout $1 = -144214548 (gdb) call write(1,"1234567890",10) $6 = -1 (gdb) p *__errno_location() $4 = 5 (gdb) whatis std::cout type = std::ostream (gdb) ptype std::cout type = std::ostream (gdb) p &std::cout $1 = (std::ostream *) 0x7ffff7dd8700 <std::cout> (gdb) ptype std::ostream type = std::ostream (gdb) p std::cout $1 = <incomplete type> (gdb) p &std::cout $3 = (std::ostream *) 0x7ffff7dd8700 <std::cout> (gdb) p *(std::ostream *)&std::cout $4 = <incomplete type> (gdb) info symbol 0x7ffff7dd8700 std::cout in section .bss of /lib64/libstdc++.so.6 (gdb) info address std::cout Symbol "std::cout" is static storage at address 0x7ffff7dd8700. (gdb) set solib-search-path /lib64 (gdb) info share 或 info sharedlibrary From To Syms Read Shared Object Library 0x00007ffff7ddbb10 0x00007ffff7df6460 Yes (*) /lib64/ld-linux-x86-64.so.2 0x00007ffff7ef4060 0x00007ffff7ef54f8 Yes /lib64/libonion.so 0x00007ffff7b2e510 0x00007ffff7b9559a Yes (*) /lib64/libstdc++.so.6 0x00007ffff77d6370 0x00007ffff7841278 Yes (*) /lib64/libm.so.6 0x00007ffff75bdaf0 0x00007ffff75cd298 Yes (*) /lib64/libgcc_s.so.1 0x00007ffff7216480 0x00007ffff735cc00 Yes (*) /lib64/libc.so.6 0x00007ffff6ff3e60 0x00007ffff6ff4960 Yes (*) /lib64/libdl.so.2 (*): Shared library is missing debugging information. (gdb) sharedlibrary libstdc Symbols already loaded for /lib64/libstdc++.so.6 (gdb) sharedlibrary libstdc++ Symbols already loaded for /lib64/libstdc++.so.6 |
# lsof -p 4442 cgihost 4442 root 0r CHR 1,3 0t0 1028 /dev/null cgihost 4442 root 1u CHR 136,2 0t0 5 /dev/pts/2 (deleted) cgihost 4442 root 2u CHR 136,2 0t0 5 /dev/pts/2 (deleted) cgihost 4442 root 3u REG 253,17 1144245 2097190 /data/cgi/log/httpserver/cgi_test.log cgihost 4442 root 4u 0000 0,9 0 6842 anon_inode cgihost 4442 root 7u REG 253,1 144 1360125 /usr/local/httpserver/bin/map/mem-cgi-bin-test cgihost 4442 root 8u unix 0xffff880006933b80 0t0 831550706 socket # pmap 4442 |
# objdump -t test|grep _ZSt4cout 0000000000601080 g O .bss 0000000000000110 _ZSt4cout@@GLIBCXX_3.4 # readelf -s /usr/lib/libstdc++.so.6|grep cout Num: Value Size Type Bind Vis Ndx Name 884: 000e3ec0 140 OBJECT GLOBAL DEFAULT 27 _ZSt4cout@@GLIBCXX_3.4 902: 000e4140 144 OBJECT GLOBAL DEFAULT 27 _ZSt5wcout@@GLIBCXX_3.4 # readelf -r /usr/lib/libstdc++.so.6|grep cout Offset Info Type Sym.Value Sym. Name 000e2b64 00038606 R_386_GLOB_DAT 000e4140 _ZSt5wcout 000e2ea0 00037406 R_386_GLOB_DAT 000e3ec0 _ZSt4cout |
如果希望能够Debug标准库中的设施,可在编译时加上开关“-D_GLIBCXX_DEBUG”。
实际上,即使不能GDB中直接得到std::cout的地址,特别是其成员_M_streambuf_state的地址,但可采取变通的办法取得。
先编写如下一小段代码,然后执行这一小段代码,取得成员_M_streambuf_state和std::cout间的偏移Offset,以方便得到std::cout地址时取得成员_M_streambuf_state的地址,以达到修改成员_M_streambuf_state值的目的。
#include <stdio.h> #include <iostream> int main() { const int n = (unsigned long)&std::cout._M_streambuf_state - (unsigned long)&std::cout; printf("offset: %d\n", n); return 0; } |
注意,编译之前需要修改标准库头文件ios_base.h,将_M_streambuf_state的类型由protected改成public。不然,应当修改小段代码成如下:
#include <stdio.h> #include <iostream> class X: public std::ostream { public: using std::ostream::_M_streambuf_state; }; int main() { X x; const int n = (unsigned long)&x._M_streambuf_state - (unsigned long)&x; printf("offset: %d\n", n); return 0; } |
当前的多数环境上,offset值一般为40。
如果是可执行程序文件,找cout的地址简单多(“0000000000601280”即为cout的内存地址):
# file xxx xxx: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped # objdump -t xxx|grep cout 0000000000601280 g O .bss 0000000000000110 _ZSt4cout@@GLIBCXX_3.4 |
这里介绍一种通用的取std::cout地址,及std::cout的_M_streambuf_state成员偏移方式。得到std::cout的地址和成员_M_streambuf_state的偏移,实际也就得到了成员_M_streambuf_state的地址,有了地址就可以控制它了。
首先,编写如下这样的一小段Hook代码:
// hooker.cpp #include <iostream> #include <stdio.h> class Hooker { public: Hooker() { // 得到std::cout的成员_M_streambuf_state偏移 const int offset = (unsigned long)&std::cout._M_streambuf_state - (unsigned long)&std::cout; FILE* fp = fopen("/tmp/hooker.txt", "w+"); fprintf(fp, "&std::cout: %p, offset: %d, _M_streambuf_state: %p\n", &std::cout, offset, &std::cout+offset); fclose(fp); } }; static Hooker __hooker; |
这小段代码的目的是为得到std::cout成员_M_streambuf_state的内存地址,以方便在GDB中控制它。将Hook代码编译成共享库:
g++ -g -o libhooker.so -fPIC -shared hooker.cpp |
如果是64位系统,需要编译成32位的共享库,则编译命令为:
g++ -m32 -g -o libhooker.so -fPIC -shared hooker.cpp |
假设将libhooker.so放在/tmp目录下,先使用GDB进入目标进程,假设目标进程ID为2019,则:
# gdb -p 2019 在GDB中加载共享库,这样共享库中的全局变量的构造函数将执行,从而可从文件/tmp/hooker.txt中得到想要的信息 (gdb) call dlopen("/tmp/libhooker.so",258) (gdb) c 假设/tmp/hooker.txt中std::cout的地址为0xf76f9ec0,成员_M_streambuf_state的偏移为24,则在GDB中可如下查看_M_streambuf_state的值: (gdb) p *(int*)(0xf76f9ec0+24) 也可在GDB中执行如下命令确认: (gdb) info symbol (0xf76f9ec0+24) 可在GDB中执行如下命令修改_M_streambuf_state的值: (gdb) set *(int*)(0xf76f9ec0+24)=1 |
掌握了Hook方法后,查明原因就十分简单了。这个方法有一个前提,目标进程有链接libdl.so,因为它提供了加载共享库函数dlopen的。确认是否链接了libdl.so方法,使用ldd即可:
$ ldd z linux-vdso.so.1 => (0x00007ffde7822000) /$LIB/libonion.so => /lib64/libonion.so (0x00007f4872630000) libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007f487220f000) libm.so.6 => /lib64/libm.so.6 (0x00007f4871f0d000) libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f4871cf7000) libc.so.6 => /lib64/libc.so.6 (0x00007f4871933000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f487172f000) /lib64/ld-linux-x86-64.so.2 (0x00007f4872517000) |
C++ CGI报“资源访问错误”问题分析的更多相关文章
- 避免图片路径访问405,可以用图片控件来显示局部相对路径,不需要域名就不会出现jpg静态资源访问错误
<asp:Image ID="Image1" runat="server"/> protected void Page_Load(object se ...
- Entity Framework 数据并发访问错误原因分析与系统架构优化
博客地址 http://blog.csdn.net/foxdave 本文主要记录近两天针对项目发生的数据访问问题的分析研究过程与系统架构优化,我喜欢说通俗的白话,高手轻拍 1. 发现问题 系统新模块上 ...
- PLC_SIM 出现I/O访问错误-技术论坛-工业支持中心-西门子中国
PLC_SIM 作为SIEMENS S7-300/400 系列PLC 的仿真软件,在使用时需要有些注意事项,毕竟任何的仿真软件和真正的设备还是有一定差异的,由此而产生的误会经常会令很多客户摸不着头脑, ...
- 在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案
转自:http://www.jianshu.com/p/89687f618837 原因分析 当我们在Android依赖库中使用switch-case语句访问资源ID时会报如下图所示的错误,报的错误 ...
- 前段时间,接手一个项目使用的是原始的jdbc作为数据库的访问,发布到服务器上在运行了一段时间之后总是会出现无法访问的情况,登录到服务器,查看tomcat日志发现总是报如下的错误。 Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected est
前段时间,接手一个项目使用的是原始的jdbc作为数据库的访问,发布到服务器上在运行了一段时间之后总是会出现无法访问的情况,登录到服务器,查看tomcat日志发现总是报如下的错误. Caused by: ...
- Spring源码分析——资源访问利器Resource之实现类分析
今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...
- curl使用post方式访问Spring Cloud gateway报time out错误
公司老的项目使用是php,要进行重构.其他团队使用php curl函数使用post方式调用Spring Cloud gateway 报time out错误. 但是使用postman测试是没有任何问题, ...
- loadFileSystems error & ExceptionUtils错误原因分析
loadFileSystems error & ExceptionUtils错误原因分析 一见 2014/5/7 C/C++程序通过hdfs.h访问HDFS,运行时遇到如下错误,会是什么原因了 ...
- LoadRunner11学习记录五 -- 错误提示分析
LoadRunner测试结果具体分析: 一.错误提示分析 分析实例: 1.Error: Failed to connect to server “172.17.7.230″: [10060] Con ...
随机推荐
- 企业微信同步LDAP
1.需求 定期同步企业微信的用户信息到 LDAP 中,当有新用户时,会自动发送LDAP的账号密码给该用户邮箱. 2.环境 python 3.x 需要安装两个模块 pip install ldap3 r ...
- TPM(ThinkPHPMobile)使用简明教程
TPM还有很多特性,它不仅能和ThinkPHP结合,也可以结合自己已有的接口.还有一些附件插件帮助我们实现一些常用功能 一.基础知识 1 手机APP的类型 移动端的应用有这几种:WebApp,Nati ...
- Spark Core知识点复习-1
Day1111 Spark任务调度 Spark几个重要组件 Spark Core RDD的概念和特性 生成RDD的两种类型 RDD算子的两种类型 算子练习 分区 RDD的依赖关系 DAG:有向无环图 ...
- 『Norma 分治』
Norma Description Input Format 第1行,一个整数N: 第2~n+1行,每行一个整数表示序列a. Output Format 输出答案对10^9取模后的结果. Sample ...
- golang --os系统包
环境变量 Environ 获取所有环境变量, 返回变量列表 func Environ() []string package main import ( "fmt" "os ...
- 线程池---Day26
线程池思想概述 当我们要使用线程的时候就去创建一个线程时,虽然实现方便,但是会出现问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率, ...
- topshelf注册服务
1.需要从nutget上获取toshelf配置 2.代码 using Common.Logging; using Quartz; using Quartz.Impl; using System; us ...
- 三款免费好用的Gif录屏神器
三款免费好用的Gif录屏神器 1. 免费开源的GIF录制工具ScreenToGif 官网地址:http://www.screentogif.com/ ScreenToGif,国外免费开源小巧实用的Gi ...
- ES6 Promise对象(七)
一.Promise介绍1.Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果2.Promise可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函 ...
- web-api POST body object always null
If the any of values of the request's JSON object are not the same type as expected by the service ...