一线上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有两个具体的子类:stringbuffilebuf

// 对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报“资源访问错误”问题分析的更多相关文章

  1. 避免图片路径访问405,可以用图片控件来显示局部相对路径,不需要域名就不会出现jpg静态资源访问错误

    <asp:Image ID="Image1" runat="server"/> protected void Page_Load(object se ...

  2. Entity Framework 数据并发访问错误原因分析与系统架构优化

    博客地址 http://blog.csdn.net/foxdave 本文主要记录近两天针对项目发生的数据访问问题的分析研究过程与系统架构优化,我喜欢说通俗的白话,高手轻拍 1. 发现问题 系统新模块上 ...

  3. PLC_SIM 出现I/O访问错误-技术论坛-工业支持中心-西门子中国

    PLC_SIM 作为SIEMENS S7-300/400 系列PLC 的仿真软件,在使用时需要有些注意事项,毕竟任何的仿真软件和真正的设备还是有一定差异的,由此而产生的误会经常会令很多客户摸不着头脑, ...

  4. 在Android library中不能使用switch-case语句访问资源ID的原因分析及解决方案

    转自:http://www.jianshu.com/p/89687f618837 原因分析   当我们在Android依赖库中使用switch-case语句访问资源ID时会报如下图所示的错误,报的错误 ...

  5. 前段时间,接手一个项目使用的是原始的jdbc作为数据库的访问,发布到服务器上在运行了一段时间之后总是会出现无法访问的情况,登录到服务器,查看tomcat日志发现总是报如下的错误。    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected est

    前段时间,接手一个项目使用的是原始的jdbc作为数据库的访问,发布到服务器上在运行了一段时间之后总是会出现无法访问的情况,登录到服务器,查看tomcat日志发现总是报如下的错误. Caused by: ...

  6. Spring源码分析——资源访问利器Resource之实现类分析

    今天来分析Spring的资源接口Resource的各个实现类.关于它的接口和抽象类,参见上一篇博文——Spring源码分析——资源访问利器Resource之接口和抽象类分析 一.文件系统资源 File ...

  7. curl使用post方式访问Spring Cloud gateway报time out错误

    公司老的项目使用是php,要进行重构.其他团队使用php curl函数使用post方式调用Spring Cloud gateway 报time out错误. 但是使用postman测试是没有任何问题, ...

  8. loadFileSystems error & ExceptionUtils错误原因分析

    loadFileSystems error & ExceptionUtils错误原因分析 一见 2014/5/7 C/C++程序通过hdfs.h访问HDFS,运行时遇到如下错误,会是什么原因了 ...

  9. LoadRunner11学习记录五 -- 错误提示分析

    LoadRunner测试结果具体分析: 一.错误提示分析  分析实例: 1.Error: Failed to connect to server “172.17.7.230″: [10060] Con ...

随机推荐

  1. 认识MicroBit

    MicroBit是BBC(英国广播公司),为孩子们推出一款开发板,或者叫控制板,可以简单地理解为通过这块电子板,可以控制接在其外围的电子模板,可以读入数据,也可以输出数据,模拟的或数字的数据.这样一来 ...

  2. javascirpt的json.stringify()方法在IE浏览器兼容性模式下出错的原因与解决办法

    今天开机混底薪的时候遇到一个JSON.stringify()在IE浏览器兼容模式下的问题. 问题描述 一个弹窗选择的功能原来好好的,突然就不行了. 想要调试调试不了,报错信息也看不到(一开F12这破I ...

  3. Docker File

  4. [转] JS中arr.forEach()如何跳出循环

    我们都知道for循环里要跳出整个循环是使用break,但在数组中用forEach循环如要退出整个循环呢?使用break会报错,使用return也不能跳出循环. 使用break将会报错: var arr ...

  5. PyTorch 之 Datasets

    实现一个定制的 Dataset 类 Dataset 类是 PyTorch 图像数据集中最为重要的一个类,也是 PyTorch 中所有数据集加载类中应该继承的父类.其中,父类的两个私有成员函数必须被重载 ...

  6. 使用Fiddler抓取手机APP数据包--360WIFI

    使用Fiddler抓取手机APP流量--360WIFI 操作步骤:1.打开Fiddler,Tools-Fiddler Options-Connections,勾选Allow remote comput ...

  7. Angular4项目运行时URL自动加#方法

    import {HashLocationStrategy , LocationStrategy} from '@angular/common'; @NgModule({   declarations: ...

  8. 用SWFUpload上传图片小例子

    在开发项目中,经常会用到上传图片,接下来我就用一种简单的方式给大家分享一下使用SWFUpload的方式上传图片. 1.在网站根目录下新建一个SWFUpload文件夹,把下载的组建放在SWFUpload ...

  9. 人生物语——哲海拾贝

         如今的这个社会,物欲横流.纸醉金迷.浮躁不安是这个时代的主旋律,在这样一个浮华年代的大染缸里,每个人内心都有那么一颗浮躁不安分的种子,或许它才开始发芽,或许它已经占据了你的心灵,人生当中追求 ...

  10. 苹果电脑Mac系统如何安装Adobe Flash Player

    一)安装/更新Adobe Flash Player 开系统偏好设置 , Flash player  更新,立即检查/立刻安装: Flash插件官方每月常规更新1~2次,为避免频繁过期,建议设置为允许A ...