1. 程式人生 > >apache後臺cgi掛掉之後現場還原

apache後臺cgi掛掉之後現場還原

bin文件 amp -h ant require cat gprof 比較 https

一、apache的實現
對於apache的實現,默認配置的時候是不支持cgi模式的,這裏的cgi模式就是cgid_mod的加載。如果沒有加載該模塊,當把該文件放入cgi_bin文件之後,從瀏覽器獲得該文件的時候,httpd並不是執行該文件並將文件的輸出返回,有意思的是,瀏覽器將會直接將請求的cgi文件整個下載下來。這裏只是描述了現象,具體的細節分析還是比較周折的,所以暫時就不展開了。
二、cgid的模式
等待操作
(gdb) bt
#0 0x0095a424 in __kernel_vsyscall ()
#1 0x003990d1 in accept () from /lib/libpthread.so.0
#2 0x0016a41b in cgid_server (data=0x80f3ea8) at mod_cgid.c:686
#3 0x0016ad46 in cgid_start (p=0x80cf0a8, main_server=0x80f3ea8,
procnew=0x80eebe8) at mod_cgid.c:876
#4 0x0016b06c in cgid_init (p=0x80cf0a8, plog=0x812faa0, ptemp=0x8133ab0,
main_server=0x80f3ea8) at mod_cgid.c:939
#5 0x0808ab6f in ap_run_post_config (pconf=0x80cf0a8, plog=0x812faa0,
ptemp=0x8133ab0, s=0x80f3ea8) at config.c:105
#6 0x08068e41 in main (argc=2, argv=0xbffff3b4) at main.c:765
(gdb)
│ │ └─gdb,23680 -p 23597
│ │ └─httpd,23903 -X 整個是主進程,也就是在命令行中啟動的 httpd進程
│ │ ├─httpd,23904 -X 整個就是我們所熟悉的cgid進程,大家看cgid模塊的代碼可以知道,cgid是會以單獨的進程形式存在的,或者更加形而上的說,它是通過fork創建的新進程
│ │ │ ├─first.pl,23951 /usr/local/apache2/cgi-bin/first.pl
│ │ │ ├─first.pl,23959 /usr/local/apache2/cgi-bin/first.pl
│ │ │ ├─first.pl,23976 /usr/local/apache2/cgi-bin/first.pl
│ │ │ └─mysleeper,23975

(gdb) bt
#0 0x0095a424 in __kernel_vsyscall ()
#1 0x003990d1 in accept () from /lib/libpthread.so.0 cgid成為單獨進程之後,它在一個和主進程預定好的unix socket上執行accept操作,等待不同的worker 進程connect過來,connect過來的進程將請求的內容發送給cgid,cgid然後fork出一個子進程,該進程就是cgi中指定的可執行文件,然後此時cgid的接入功能就算完成了,此時worker進程和新創建的cgi進程之間就可以通過socket來交互了,因為此時worker線程的socket端經過cgid的accept已經轉給了真正的cgi進程,它們已經聯通,此時從cgid從整個事件中退出
#2 0x0016a41b in cgid_server (data=0x80f3ea8) at mod_cgid.c:686
#3 0x0016ad46 in cgid_start (p=0x80cf0a8, main_server=0x80f3ea8,
procnew=0x80eebe8) at mod_cgid.c:876
#4 0x0016b06c in cgid_init (p=0x80cf0a8, plog=0x812faa0, ptemp=0x8133ab0,
main_server=0x80f3ea8) at mod_cgid.c:939
#5 0x0808ab6f in ap_run_post_config (pconf=0x80cf0a8, plog=0x812faa0,
ptemp=0x8133ab0, s=0x80f3ea8) at config.c:105
#6 0x08068e41 in main (argc=2, argv=0xbffff3b4) at main.c:765
(gdb)
(gdb) info thread
* 1 Thread 0xb7fe79c0 (LWP 23904) 0x0095a424 in __kernel_vsyscall ()
(gdb)

cgi的操作
(gdb) bt
#0 0x0039b514 in fork () from /lib/libpthread.so.0
#1 0x0028bef4 in apr_proc_create (new=0x81aff60,
progname=0x81b00b0 "/usr/local/apache2/cgi-bin/mysleeper", args=0x81b0a20,
env=0x81b00f8, attr=0x81b06b0, pool=0x81afd68)
at threadproc/unix/proc.c:391
#2 0x0016ab3c in cgid_server (data=0x80f3ea8) at mod_cgid.c:817
#3 0x0016ad46 in cgid_start (p=0x80cf0a8, main_server=0x80f3ea8,
procnew=0x80eebe8) at mod_cgid.c:876
#4 0x0016b06c in cgid_init (p=0x80cf0a8, plog=0x812faa0, ptemp=0x8133ab0,
main_server=0x80f3ea8) at mod_cgid.c:939
#5 0x0808ab6f in ap_run_post_config (pconf=0x80cf0a8, plog=0x812faa0,
ptemp=0x8133ab0, s=0x80f3ea8) at config.c:105
#6 0x08068e41 in main (argc=2, argv=0xbffff3b4) at main.c:765
(gdb)
httpd-2.4.2\modules\generators\mod_cgid.c
static apr_status_t handle_exec(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
static int cgid_handler(request_rec *r)
{
int retval, nph, dbpos;
三、cgi進程退出之後誰來執行子進程的waitpid操作
例如說,cgi不小心自己掛掉了,比方說coredump了,此時前臺將會如何處理呢?行為未定義不是答案。
SigQ: 0/8192
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001011000 這裏父進程是直接忽略了子進程的退出
SigCgt: 0000000180000001
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: ffffffffffffffff
Cpus_allowed: 1
Cpus_allowed_list: 0
Mems_allowed: 1
Mems_allowed_list: 0
voluntary_ctxt_switches: 199
nonvoluntary_ctxt_switches: 15
[root@Harry ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
[root@Harry ~]#
也就是cgid直接忽略了子進程的退出,從而由內核直接回收掉進程,而不用等待子進程被用戶態回收。當然此時我可以找到內核的相關代碼證明給大家看,就像《加州招待所》中所唱的:light up the candle and she show me the way,但是這個太平常了所以就不摘錄了。
四、cgi 不行coredump之後如何處理
從上面可以看到,子進程的生死是沒有人介意的,但是用戶在意,因為用戶在等待這個cgi的返回,並且worker線程也在等待這個cgi的返回。等待者如何被喚醒呢?這個比較簡單,因為進程退出之後,內核會關閉進程打開的所有文件,而對於socket來說,如果有人在該socket上執行read操作,那麽此時這個癡情者將會被喚醒,所以它不用等待子進程的SIGCHILD的喚醒就可以醒過來。
此時進程被喚醒之後,它一定還處於非常懵懂的狀態,就好像你早上正睡得很香,突然在你窗口放一個鐵桶,然後在裏面放一串1W響的鞭炮一樣,從震驚中醒來。此時worker像正常的被喚醒一樣去檢測cgi的返回,就像你被吵醒了之後依然正常去上班一樣。此時掃描cgi輸出的直接接口為httpd-2.4.2\server\util_script.c
AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer,
int (*getsfunc) (char *, int, void *),
void *getsfunc_data,
int module_index)
while (1) {

int rv = (*getsfunc) (w, MAX_STRING_LEN - 1, getsfunc_data); 此時雖然文件關閉,但是cgi沒有輸出,此時掃描cgi輸出的腳本長度為零,走到下面的輸出就會返回HTTP_INTERNAL_SERVER_ERROR錯誤,這個錯誤在httpd中對應的內容為
#define HTTP_INTERNAL_SERVER_ERROR 500

if (rv == 0) {
const char *msg = "Premature end of script headers";
if (first_header)
msg = "End of script output before headers";
ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
"%s: %s", msg,
apr_filepath_name_get(r->filename));
return HTTP_INTERNAL_SERVER_ERROR;
}
else if (rv == -1) {
ap_log_rerror(SCRIPT_LOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,
"Script timed out before returning headers: %s",
apr_filepath_name_get(r->filename));
return HTTP_GATEWAY_TIME_OUT;
}
此時apache server的配置中給出了這種情況發生了之後你可以執行的操作,但是這個配置默認是被註釋掉的,其內容為
409 # Some examples:
410 #ErrorDocument 500 "The server made a boo boo."
411 #ErrorDocument 404 /missing.html
412 #ErrorDocument 404 "/cgi-bin/missing_handler.pl"

展示一下默認的錯誤提示

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at [email protected] to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.
系統錯誤日誌中內容
34 [Sat Jan 26 00:50:33.845682 2013] [cgid:error] [pid 24329:tid 2822667120 ] [client 192.168.203.1:4076] End of script output before headers: segv
35 [Sat Jan 26 00:51:08.433898 2013] [cgid:error] [pid 24329:tid 2843646832 ] [client 192.168.203.1:4091] End of script output before headers: segv
36 [Sat Jan 26 00:58:40.453014 2013] [cgid:error] [pid 24329:tid 2864626544 ] [client 192.168.203.1:4254] End of script output before headers: segv


五、apache中默認錯誤提示
httpd-2.4.2\modules\http\http_protocol.c
由於個人對於錯誤非常敏感和感興趣,所以整個摘錄過來了,希望你們也會喜歡啊,親,so,enjoy。

/* construct and return the default error message for a given
* HTTP defined error code
*/
static const char *get_canned_error_string(int status,
request_rec *r,
const char *location)
{
apr_pool_t *p = r->pool;
const char *error_notes, *h1, *s1;

switch (status) {
case HTTP_MOVED_PERMANENTLY:
case HTTP_MOVED_TEMPORARILY:
case HTTP_TEMPORARY_REDIRECT:
return(apr_pstrcat(p,
"<p>The document has moved <a href=\"",
ap_escape_html(r->pool, location),
"\">here</a>.</p>\n",
NULL));
case HTTP_SEE_OTHER:
return(apr_pstrcat(p,
"<p>The answer to your request is located "
"<a href=\"",
ap_escape_html(r->pool, location),
"\">here</a>.</p>\n",
NULL));
case HTTP_USE_PROXY:
return(apr_pstrcat(p,
"<p>This resource is only accessible "
"through the proxy\n",
ap_escape_html(r->pool, location),
"<br />\nYou will need to configure "
"your client to use that proxy.</p>\n",
NULL));
case HTTP_PROXY_AUTHENTICATION_REQUIRED:
case HTTP_UNAUTHORIZED:
return("<p>This server could not verify that you\n"
"are authorized to access the document\n"
"requested. Either you supplied the wrong\n"
"credentials (e.g., bad password), or your\n"
"browser doesn‘t understand how to supply\n"
"the credentials required.</p>\n");
case HTTP_BAD_REQUEST:
return(add_optional_notes(r,
"<p>Your browser sent a request that "
"this server could not understand.<br />\n",
"error-notes",
"</p>\n"));
case HTTP_FORBIDDEN:
return(apr_pstrcat(p,
"<p>You don‘t have permission to access ",
ap_escape_html(r->pool, r->uri),
"\non this server.</p>\n",
NULL));
case HTTP_NOT_FOUND:
return(apr_pstrcat(p,
"<p>The requested URL ",
ap_escape_html(r->pool, r->uri),
" was not found on this server.</p>\n",
NULL));
case HTTP_METHOD_NOT_ALLOWED:
return(apr_pstrcat(p,
"<p>The requested method ",
ap_escape_html(r->pool, r->method),
" is not allowed for the URL ",
ap_escape_html(r->pool, r->uri),
".</p>\n",
NULL));
case HTTP_NOT_ACCEPTABLE:
s1 = apr_pstrcat(p,
"<p>An appropriate representation of the "
"requested resource ",
ap_escape_html(r->pool, r->uri),
" could not be found on this server.</p>\n",
NULL);
return(add_optional_notes(r, s1, "variant-list", ""));
case HTTP_MULTIPLE_CHOICES:
return(add_optional_notes(r, "", "variant-list", ""));
case HTTP_LENGTH_REQUIRED:
s1 = apr_pstrcat(p,
"<p>A request of the requested method ",
ap_escape_html(r->pool, r->method),
" requires a valid Content-length.<br />\n",
NULL);
return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
case HTTP_PRECONDITION_FAILED:
return(apr_pstrcat(p,
"<p>The precondition on the request "
"for the URL ",
ap_escape_html(r->pool, r->uri),
" evaluated to false.</p>\n",
NULL));
case HTTP_NOT_IMPLEMENTED:
s1 = apr_pstrcat(p,
"<p>",
ap_escape_html(r->pool, r->method), " to ",
ap_escape_html(r->pool, r->uri),
" not supported.<br />\n",
NULL);
return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
case HTTP_BAD_GATEWAY:
s1 = "<p>The proxy server received an invalid" CRLF
"response from an upstream server.<br />" CRLF;
return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
case HTTP_VARIANT_ALSO_VARIES:
return(apr_pstrcat(p,
"<p>A variant for the requested "
"resource\n<pre>\n",
ap_escape_html(r->pool, r->uri),
"\n</pre>\nis itself a negotiable resource. "
"This indicates a configuration error.</p>\n",
NULL));
case HTTP_REQUEST_TIME_OUT:
return("<p>Server timeout waiting for the HTTP request from the client.</p>\n");
case HTTP_GONE:
return(apr_pstrcat(p,
"<p>The requested resource<br />",
ap_escape_html(r->pool, r->uri),
"<br />\nis no longer available on this server "
"and there is no forwarding address.\n"
"Please remove all references to this "
"resource.</p>\n",
NULL));
case HTTP_REQUEST_ENTITY_TOO_LARGE:
return(apr_pstrcat(p,
"The requested resource<br />",
ap_escape_html(r->pool, r->uri), "<br />\n",
"does not allow request data with ",
ap_escape_html(r->pool, r->method),
" requests, or the amount of data provided in\n"
"the request exceeds the capacity limit.\n",
NULL));
case HTTP_REQUEST_URI_TOO_LARGE:
s1 = "<p>The requested URL‘s length exceeds the capacity\n"
"limit for this server.<br />\n";
return(add_optional_notes(r, s1, "error-notes", "</p>\n"));
case HTTP_UNSUPPORTED_MEDIA_TYPE:
return("<p>The supplied request data is not in a format\n"
"acceptable for processing by this resource.</p>\n");
case HTTP_RANGE_NOT_SATISFIABLE:
return("<p>None of the range-specifier values in the Range\n"
"request-header field overlap the current extent\n"
"of the selected resource.</p>\n");
case HTTP_EXPECTATION_FAILED:
s1 = apr_table_get(r->headers_in, "Expect");
if (s1)
s1 = apr_pstrcat(p,
"<p>The expectation given in the Expect request-header\n"
"field could not be met by this server.\n"
"The client sent<pre>\n Expect: ",
ap_escape_html(r->pool, s1), "\n</pre>\n",
NULL);
else
s1 = "<p>No expectation was seen, the Expect request-header \n"
"field was not presented by the client.\n";
return add_optional_notes(r, s1, "error-notes", "</p>"
"<p>Only the 100-continue expectation is supported.</p>\n");
case HTTP_UNPROCESSABLE_ENTITY:
return("<p>The server understands the media type of the\n"
"request entity, but was unable to process the\n"
"contained instructions.</p>\n");
case HTTP_LOCKED:
return("<p>The requested resource is currently locked.\n"
"The lock must be released or proper identification\n"
"given before the method can be applied.</p>\n");
case HTTP_FAILED_DEPENDENCY:
return("<p>The method could not be performed on the resource\n"
"because the requested action depended on another\n"
"action and that other action failed.</p>\n");
case HTTP_UPGRADE_REQUIRED:
return("<p>The requested resource can only be retrieved\n"
"using SSL. The server is willing to upgrade the current\n"
"connection to SSL, but your client doesn‘t support it.\n"
"Either upgrade your client, or try requesting the page\n"
"using https://\n");
case HTTP_INSUFFICIENT_STORAGE:
return("<p>The method could not be performed on the resource\n"
"because the server is unable to store the\n"
"representation needed to successfully complete the\n"
"request. There is insufficient free space left in\n"
"your storage allocation.</p>\n");
case HTTP_SERVICE_UNAVAILABLE:
return("<p>The server is temporarily unable to service your\n"
"request due to maintenance downtime or capacity\n"
"problems. Please try again later.</p>\n");
case HTTP_GATEWAY_TIME_OUT:
return("<p>The gateway did not receive a timely response\n"
"from the upstream server or application.</p>\n");
case HTTP_NOT_EXTENDED:
return("<p>A mandatory extension policy in the request is not\n"
"accepted by the server for this resource.</p>\n");
default: /* HTTP_INTERNAL_SERVER_ERROR */
/*
* This comparison to expose error-notes could be modified to
* use a configuration directive and export based on that
* directive. For now "*" is used to designate an error-notes
* that is totally safe for any user to see (ie lacks paths,
* database passwords, etc.)
*/
if (((error_notes = apr_table_get(r->notes,
"error-notes")) != NULL)
&& (h1 = apr_table_get(r->notes, "verbose-error-to")) != NULL
&& (strcmp(h1, "*") == 0)) {
return(apr_pstrcat(p, error_notes, "<p />\n", NULL));
}
else {
return(apr_pstrcat(p,
"<p>The server encountered an internal "
"error or\n"
"misconfiguration and was unable to complete\n"
"your request.</p>\n"
"<p>Please contact the server "
"administrator at \n ",
ap_escape_html(r->pool,
r->server->server_admin),
" to inform them of the time this "
"error occurred,\n"
" and the actions you performed just before "
"this error.</p>\n"
"<p>More information about this error "
"may be available\n"
"in the server error log.</p>\n",
NULL));
}
/*
* It would be nice to give the user the information they need to
* fix the problem directly since many users don‘t have access to
* the error_log (think University sites) even though they can easily
* get this error by misconfiguring an htaccess file. However, the
* e error notes tend to include the real file pathname in this case,
* which some people consider to be a breach of privacy. Until we
* can figure out a way to remove the pathname, leave this commented.
*
* if ((error_notes = apr_table_get(r->notes,
* "error-notes")) != NULL) {
* return(apr_pstrcat(p, error_notes, "<p />\n", NULL);
* }
* else {
* return "";
* }
*/
}
}

apache後臺cgi掛掉之後現場還原