问题的起因
前几天为了调试gsoap生成的server端代码(实际是一个cgi程序),一直没有找到好的调试方法,网上搜也没有找到一个切实可行的方法,于是分析了一下cgi的原理,终于找到一个调试CGI的办法。
CGI程序的本质
其实cgi就是一个没有界面的exe程序,cgi程序从stdin读取消息,从 stdout输出结果,一个cgi程序往往是部署在一个http server上,比如iis或apache,http server在接到cgi请求以后会创建一个线程,在线程里用CreatePipe创建一个匿名管道:
The CreatePipe function creates an anonymous pipe, and returns handles to the read and write ends of the pipe.
BOOL CreatePipe(
PHANDLE hReadPipe, // read handle
PHANDLE hWritePipe, // write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // security attributes
DWORD nSize // pipe size
);
该函数在创建匿名管道的同时返回两个句柄:管道读句柄hReadPipe和管道写句柄hWritePipe,通过hReadPipe和hWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。
然后,创建出cgi进程,并替换cgi进程的标准输入、标准输出和标准错误句柄。cgi进程运行以后从stdin(读管道句柄)读取http server中穿过来的消息,然后根据请求执行响应的操作,最后将结果通过stdout(实际已经被与客户端对应的socket句柄替换)输出到http server或直接输出给客户端,之后cgi进程就结束了。
下面的是从msdn里的http server例子HTTPSVR中摘出来的,显示了启动一个CGI程序的过程:
UINT CGIThread( LPVOID pvParam )
{
......
HANDLE hWritePipe, hReadPipe;
// create a pipe we'll use for STDIN....
CreatePipe( &hReadPipe, &hWritePipe, &g_sa, 0 ) )
......
//创建CGI进程
PROCESS_INFORMATION pi;
STARTUPINFO si = {0};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = hReadPipe;
si.hStdOutput = pReqSock->m_hFile;
si.hStdError = pReqSock->m_hFile;
bOk = CreateProcess( NULL, strCmdLine.GetBuffer(1),
NULL, NULL, TRUE,
dwCreateFlags, pEnv,
strDir, &si, &pi );
......
// send the body of the post to the stdin....
WriteFile( hWritePipe, pRequest->m_baBody.GetData(),
pRequest->m_cbBody, &dwWritten, NULL );
......
//结果输出
//由于直接将与客户端连接的socket句柄赋值给了si.hStdOutput
//所以cgi程序输出的时候直接将结果写回给客户端了
//所以http server在这里不需要再读取cgi的输出了
}
调试CGI程序
了解了CGI的本质,调试的思路就有了,就是调试一个事先执行起来的exe程序嘛,用VC6中的“Build|Start Debug|Attach to process”方法调试就搞定了。需要注意的就是cgi运行的很快,往往是当你Attach这个进程的时候,它往往已经执行完毕退出了,即便不退出可能您所关心的代码部分已经执行过了,这个问题可以通过在cgi的main函数开头添加一个死循环来解决,如下:
int main(int argc, char **argv)
{
while(true)
{
Sleep(2000);
}
//真正的cgi代码,比如:
int m, s; /* master and slave sockets */
struct soap soap;
soap_init(&soap);
......
}
这样当CGI进程起来以后,就会进入while(true)循环,这时你就可以很从容的用VC调试器 Attach到cgi进程,然后打开对应的源码,在Sleep(2000)处设置一个断点,就可以看到cgi进程乖乖进入断点了,在soap_init(&soap)处使用“Set Next Statement”命令就可以跳转到soap_init行向下执行了。