zoukankan      html  css  js  c++  java
  • C++ CGI报“资源访问错误”问题分析

    一线上CGI偶发性会报“资源访问错误”,经过分析得出是因为CgiHost没有读取到CGI的任务输出,即CGI运行完成后连HTTP头都没有一点输出。

    然而实际上,不可能没有任何输出,因为CGI至少有无条件的HTTP头部分输出,因此问题是输出丢失了。CGICgiHost间是通过重定向CGI的标准输出到Unix套接字进行交互的,如果这个套接字坏了,或者CGI的标准输出关闭了,自然不会有任何输出。但经测试,如果是关闭了套接字,报的错误不一样,因此直接排除这个可能。

    经调查,该CGI的输出使用是C++库的std::cout,不是printf这套CI/O,初步推断是std::cout对象的状态值不是goodbit。不是goodbit主要分三种情况:

    1) 人为置为badbit等;

    2) 程序有越界造成状态为badbitfailbit等;

    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_statestd::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);

      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);

      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 ",

          &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进入目标进程,假设目标进程ID2019,则:

    # 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)

  • 相关阅读:
    poj 3666 Making the Grade
    poj 3186 Treats for the Cows (区间dp)
    hdu 1074 Doing Homework(状压)
    CodeForces 489C Given Length and Sum of Digits...
    CodeForces 163A Substring and Subsequence
    CodeForces 366C Dima and Salad
    CodeForces 180C Letter
    CodeForces
    hdu 2859 Phalanx
    socket接收大数据流
  • 原文地址:https://www.cnblogs.com/aquester/p/11818710.html
Copyright © 2011-2022 走看看