2008年12月22日
当我们使用ProcessExplorer的时候,发现它可以得到各个进程的启动参数(也就是在查看一个进程的属性里,在commandline里面显示的内容)。但是翻遍了MSDN也没有对应的API可以去作这样的事情,最开始的时候很无奈,只好先用内存查看器,看一下commmandline在内存啥位置,然后再用ReadProcessMemory去读一个尽可能大的块出来,虽然简单,但是不可靠,而且有时候读出来的东西后面是一堆无用的数据,影响美观。
经常不断的搜索,终于找到了方法:
1.先用OpenProcess 打开目标进程的进程空间,得到句柄
2.使用NtQueryInformationProcess这个API去读取进程里面的PE块的基地址也就是:PebBaseAddress
3.继续使用ReadProcessMemory,从这个PebBaseAddress,开始读取PEB(PE Block),这时候可以得到ProcessParameters,进程的参数地址
4.继续使用ReadProcessMemory,从这个ProcessParameters,开始读取PROCESS_PARAMETERS,这时候可以得到CommandLine.Length和CommandLine.Buffer,也就是启动参数的长度和启动参数的地址。
5.最后再使用ReadProcessMemory,根据记动参数的地址和长度去读取启动参数。特别要注意的事情是,如果在unicode的系统中,这时候读到的启动参数也是unicode的,所以得定义对应的字串类型去读取,不然打印出来的字串只有第一个字母(比如说参数是:abc,如果用ansi的字串,结果就是:a\0b\0c\0,\0这个就表示字串的结束了)。
最后,如果发现在第2步的时候出现读取错误,这时候应该是程序没有debug的权限了,可以用以下方法来提升程序的权限:
1.先用LookupPrivilegeValue来查看能否拥有:SeDebugPrivilege这个权限
2.如果可以,就用以下代码来提升权限:
Privileges.Privileges[0].Luid:=DebugNameValue;
Privileges.Privileges[0].Attributes:=SE_PRIVILEGE_ENABLED;
Result:=AdjustTokenPrivileges(TokenHandle,False,Privileges,SizeOf(Privileges),nil,RetLen);
2008年12月12日
最近突然发现招行的网银不好使了,输入密码后,提示:无效用户,请重新输入#21。于是以为是恢复的证书坏掉了,把用户删掉重新恢复。经过几天痛苦的尝试,终于把恢复问题的答案给答对了。重建了帐户,再次登录还是这样的错误。
最后没有招了,google一下,发现原来这是招行网银的一个防盗策略:如果里面的密码不是本机的键盘输入的话,就作出这样的提示。偶在本本的外挂键盘下输入的密码,所以不成功。改用本本自己的键盘输入就没有问题了。
2008年12月8日
前一阵子,项目中的一个页面每秒只能处理300次,而这个页面的逻辑也不复杂,就是根据条件拼出一个字串然后输出。开始以为这里面逻辑太复杂,所以有问题。不过后面发现了vs里面带了性能分析工具,于是抱着试试看的想法,作了一下性能分析。最后的结果让人大吃一惊:string.format这个操作竟然用掉了一半的时间,为啥它会这么费时间呢?为了真相,我用.net reflector查看了string的实现:
public static string Format(IFormatProvider provider, string format, params object[] args)
{
if ((format == null) || (args == null))
{
throw new ArgumentNullException((format == null) ? "format" : "args");
}
StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
builder.AppendFormat(provider, format, args);
return builder.ToString();
}
很让人吃惊,string.format竟然是调用了StringBuilder的AppendFormat来实现的。再继续根下去(这个源码只是通过IL得来的,可能和原始的不太一样,但是差不多了),注意里面的红色的那句:
public StringBuilder AppendFormat(IFormatProvider provider, string format, params object[] args)
{
int num3;
if ((format == null) || (args == null))
{
throw new ArgumentNullException((format == null) ? "format" : "args");
}
char[] chArray = format.ToCharArray(0, format.Length);
int index = 0;
int length = chArray.Length;
char ch = '\0';
ICustomFormatter formatter = null;
if (provider != null)
{
formatter = (ICustomFormatter) provider.GetFormat(typeof(ICustomFormatter));
}
Label_004E:
num3 = index;
int num4 = index;
while (index < length)
{
ch = chArray[index];
index++;
if (ch == '}')
{
if ((index < length) && (chArray[index] == '}'))
{
index++;
}
else
{
FormatError();
}
}
if (ch == '{')
{
if ((index < length) && (chArray[index] == '{'))
{
index++;
}
else
{
index--;
break;
}
}
chArray[num4++] = ch;
}
if (num4 > num3)
{
this.Append(chArray, num3, num4 - num3);
}
if (index == length)
{
return this;
}
index++;
if (((index == length) || ((ch = chArray[index]) < '0')) || (ch > '9'))
{
FormatError();
}
int num5 = 0;
do
{
num5 = ((num5 * 10) + ch) - 0x30;
index++;
if (index == length)
{
FormatError();
}
ch = chArray[index];
}
while (((ch >= '0') && (ch <= '9')) && (num5 < 0xf4240));
if (num5 >= args.Length)
{
throw new FormatException(Environment.GetResourceString("Format_IndexOutOfRange"));
}
while ((index < length) && ((ch = chArray[index]) == ' '))
{
index++;
}
bool flag = false;
int num6 = 0;
if (ch == ',')
{
index++;
while ((index < length) && (chArray[index] == ' '))
{
index++;
}
if (index == length)
{
FormatError();
}
ch = chArray[index];
if (ch == '-')
{
flag = true;
index++;
if (index == length)
{
FormatError();
}
ch = chArray[index];
}
if ((ch < '0') || (ch > '9'))
{
FormatError();
}
do
{
num6 = ((num6 * 10) + ch) - 0x30;
index++;
if (index == length)
{
FormatError();
}
ch = chArray[index];
}
while (((ch >= '0') && (ch <= '9')) && (num6 < 0xf4240));
}
while ((index < length) && ((ch = chArray[index]) == ' '))
{
index++;
}
object arg = args[num5];
string str = null;
if (ch == ':')
{
index++;
num3 = index;
num4 = index;
while (true)
{
if (index == length)
{
FormatError();
}
ch = chArray[index];
index++;
switch (ch)
{
case '{':
if ((index < length) && (chArray[index] == '{'))
{
index++;
}
else
{
FormatError();
}
break;
case '}':
if ((index < length) && (chArray[index] == '}'))
{
index++;
}
else
{
index--;
if (num4 > num3)
{
str = new string(chArray, num3, num4 - num3);
}
goto Label_0253;
}
break;
}
chArray[num4++] = ch;
}
}
Label_0253:
if (ch != '}')
{
FormatError();
}
index++;
string str2 = null;
if (formatter != null)
{
str2 = formatter.Format(str, arg, provider);
}
if (str2 == null)
{
if (arg is IFormattable)
{
str2 = ((IFormattable) arg).ToString(str, provider);
}
else if (arg != null)
{
str2 = arg.ToString();
}
}
if (str2 == null)
{
str2 = string.Empty;
}
int repeatCount = num6 - str2.Length;
if (!flag && (repeatCount > 0))
{
this.Append(' ', repeatCount);
}
this.Append(str2);
if (flag && (repeatCount > 0))
{
this.Append(' ', repeatCount);
}
goto Label_004E;
}
发现里面会有new string,这时候会有新的内存分配出现,也就是说string.format会产生很多临时的string对象,这个会费时间,同时也会使GC的工作量增加.既然这里面调用了stringbuilder来实现的,那为啥不直接调用stringbuilder.append来实现。于是我就把原来的实现改成了stringbuilder的append,同时设置它初始容量为我们预期的大小,通过测试,这部分的性能提高了十倍。于是性能问题解决了。
最后,我觉得如果程序的性能很重要,而在这里面又经常有string.format的时候,还是改用stringbuilder.append来实现,虽然麻烦一些,代码也不好看,但是效果还是会很明显的。
2008年12月3日
虽然AVI的文件格式很熟悉了,根据RIFF file refence里面的说明生成的AVI拿mpc一类的播放器可以正常播放,但是windows media player却死活也播放不了。用偶的程序生成的AVI文件在目录里面也没有预览,也读不出文件里面的信息,搞得偶好郁闷。有时候甚至想把自己写的这个上千行的avibuilder删了,换成系统自带的avi相关的API来实现,不过看着那么多的API自己感觉很复杂,也没有办法支持压缩的格式,所以只好放弃。
今天无聊了,又拿起了visual dub来看avi的格式,突然我发现我生成的avi文件和正常的avi文件的文件头的差别了(左边是错误的,右边是正确的,黄颜色背景的那个):
一个极小的失误,费了好长的工夫。只怪当时看文档的时候没有多想想,怎么可能会把header的标识放到了stream上面。
修改完了后,偶的avibuidler生成的AVI终于和正常的一样了。
2008年11月8日
2008年10月25日
一个产品又开发完成了,按例又是一轮的测试:功能测试,性能测试,容量测试...眼看着一切顺利,可是在最后一环,发现有问题:系统的响应时间很长,有时候一个操作十几秒才完成。
按惯例,又得查一下资源的占用情况:应用服务器和数据库服务器的CPU并不高,内存占用也不高,磁盘的IO也不高。一切显得很恑异。通过几个回合的操作,终于解决了问题:
第一回合
抓了DUMP后,用windbg分析,发现里面有近200个工作线程,而这些工作线程的堆栈上面显示,他们要么在等待SQL的连接,要么正在读数据,总之一句话,就是卡在数据库这里了。于是想通过sqlserver2005的性能报表来查看哪些SQL最费时间,结果发现竟然没有安装这个功能,所以只好用以下SQL来实现:
| SELECT TOP 10 [Average CPU used] = total_worker_time / qs.execution_count ,[Total CPU used] = total_worker_time ,[Execution count] = qs.execution_count ,[Individual Query] = SUBSTRING (qt.text,qs.statement_start_offset/2, (CASE WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) ,[Parent Query] = qt.text ,DatabaseName = DB_NAME(qt.dbid) FROM sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt ORDER BY [Average CPU used] DESC |
发现排名前十名的SQL里有6个是这个应用里的,仔细分析里面的查询和表结构,竟然发现原来设计的一些索引在这个环境里面被删了,所以现在这几个查询变成了Table scan,难怪SQL的CPU会这么高,查询时间会这么长。把这些表的索引重新建上,这下好了。数据库的性能,索引致关重要,对于sql server来说,我们可以用查询分析器里面的“显示估计的执行计划”功能(在查询区域里面,选中SQL,右键就可以看到)来看这个查询是Table Scan还是Index scan还是Index seek还是clusted Index Seek.性能上来说,按性从好到差的顺序就是:clusted Index Seek》Index seek》Index scan》Table Scan。
重新给系统加上压力,这下好了。可是好景不长,才又压了十几分钟,响应时间又变长了。于是又得进行优化。
第二回合
正当我不知道如何是好的时候,比较有经验的同事,就发现了在系统响应时间比较长的时候,在系统的日志里面竟然有对应的错误日志,原来系统里面有未捕获的异常,当出现异常的时候,应用程序池重启了,于是这时候所有的请求都被挂起,所以响应时间比较长。修改完BUG后,继续上压力。但是好景不长,响应时间又高上来了。
第三回合
再次抓DUMP,可是发现里面的线程的执行时间并不长,基本上都是几百毫秒就完成了。真是让人头痛呀。还是那个经验丰富的同事,发现了问题。通过性能监视器,发现SQL里面的等待时间很长,也就是说SQL里面出现了锁等待。于是用SQL性能监视器,结果发现好家伙,里面的一些查询竟然已经执行了几秒钟了。于是再次打开这些查询,通过显示执行计划,发现这里面最费时间的一个XML解析,而这个同样的XML解析竟然在这里面有2个,而且这个解析发生在delete的时候出现,大家都知道在Delete数据的时候,会把表锁了,要解决问题就得缩短这个锁表的时间,于是把XML解析到一个临时表里面,然后再用临时表和要删除数据的表进行关联。通过长时间的压力测试,发现响应时间长的问题终于好了。
2008年10月20日
今天碰到了一个怪问题,在主界面上有一个方法"CreateBrowser"来动态创建webbrowser控件,同时设置一个按钮来测试这个方法,结果一切OK.然后再写一个线程,在这个线程里,通过事件来调用这个CreateBrowser方法,结果这个Browser死活不出来.try一把竟然提示:尚未调用 CoInitialize.
于是想当然地在主界面加上CoInitialize,可是,没有效果,但是在那个线程初始化或是在这个CreateBrowser方法中加它,结果是把整个程序挂在那里了.看来这问题不好解决.
只好想个办法绕过去,我的解决办法是用自定义的系统消息来实现.
网络和共享中心打不开了,没有办法设置IP了,找到了以下设置IP的命令:
C:\>netsh (然后执行netsh这个命令)
netsh>interface (netsh命令的子命令)
interface>ip (interface命令的子命令)
interface ip>set (interface ip命令的子命令)
set address - 设置指定的接口的 IP 地址或默认网关。
set dns - 设置 DNS 服务器模式和地址。
set wins - 设置 WINS 服务器模式和地址。
set address "本地连接" static 192.168.0.2 255.255.255.0 192.168.0.1
2008年9月16日
上上周装了Google Chrome后,很快就删了,头脑一发热就又去把IE7给升级成IE8了。看了一下IE8好像除了界面的颜色更柔合了一点,没有啥大的变化。但是经过几个小时的试用后,我发现IE8很好很强大,偶还常向同事推荐它。
现在说说偶觉IE8的亮点吧:
1.有强大的开发者工具,第一次看到开发者工具是在FF上面看到的,不过这些工具和IE8带的比起来,简直就像是小孩的玩具了。
里面有颜色拾取器和标尺,想想以前作网页的时候,要抄别人的页面经常得抓下图来,在PS里面取颜色和取尺寸,现在这些内置工具解决了这个问题。
开发者工具里面还带了JS的调试器,而且启用简单,容易使用,而且功能强大,无论是单独的JS文件也好,还是内嵌在网页里的JS块也好,一切都可以调试。
DOM查看器也是我所希望的那样子,虽然FF里面也有DOM查看器,不过它的显示模式让人感觉使用不方便,特别是DOM很深的时候,FF里面简直就没有办法用了。它的CSS栏里面,可以轻松地看到哪些CSS 是有效的,哪些是无效的。
2.已经开始面向W3C的标准了。这个偶也说不上它是一个进步还是一个倒退。不过作为易用性来说,IE8作的还是很不错的,因为它提供了兼容模式,一步操作就可以实现IE7和IE8模式的切换。这个我相信会让用户很方便的。对于用户来说,网页的标准是次要的,最重要的是他手上的浏览器能无误地打开他所需要的网站,并进行他所需要的操作。
差点跑题了:)
现在比比这两个浏览器了:
1.易用性:我觉得IE8要得高分,因为它能很好地兼容不同的网站,而Chrome却只能无误地打开有限的网站,而且错了也不报,让用户无所适从。
2.性能:不用怀疑,IE8确实要吃更多的内存,速度上来说,我并没有觉得太大的差别。
3.稳定性:我觉得IE8要更胜一筹,用了一两个星期了,以前打开一些老容易把浏览器卡死的网站,现在不会出现这种性况了。
再说说Chrome所说的一些新特性,其实在IE8里面早就是这样去实现的了:比如说每个TAB页单独的进程,隐私模式等等。基本上Chrome所说的亮点,在IE8里面早就存在了。
2008年9月3日
打开下载链接,发现只有四百多K ,心中在想是不是又一个基于IE内核的浏览器,不过,很快推翻了我的想法,因为旁边有一个完整版的下载,那个400K只是一个浏览器的下载器,最讨厌这种发布方式,让人白开心了一把,如果上网不方便的,下载回去装,结果发现还得接着下,那叫一个郁闷。
安装过程太傻瓜了,一点选择权都没有,不是很爽,偶的C盘没有空间了,这样装,只好玩完以后就删了。
用的时候,发现速度还可以,不过有时候CPU的使用还是比IE高,多TAB页关掉的时候,没有释放内存,这个和IE是一样的。发现里面有一个任务管理器,可以看到每个页面占用的CPU,内存等。
继续用下去,发现问题来了,兼容性是一个大问题,IE和FF都能正常跑的页面,它跑不下去了,而且没有错误控制台,也不知道是哪个JS 出的错,再试一下百度的地图,结果发现用不了。再用google的地图发现可以用,难道被屏蔽了?
最终的结论是:我一会儿就会把它删掉,再没有更好的更新前,我不会使用它。