1. 习惯成就高效能

    首先需要说明的是,我并不相信成功有什么方法论,不过生活中很多的事情确是有套路可以遵循借鉴的,正如公元前两三百年的时候,亚里士多德就说过的话: 人的行为总是一再重复,因此卓越不是单一的举动,而是习惯。 这应该就是之前我引用到说说而被某人说矫情的一句话的源头——优秀是一种习惯。 两个自身条件、机遇等等差不多的人,随着时间的流逝逐渐拉开差距,也许只是其中一个有意识地让自己形成了一些良好的习惯与套路,而另一个迷迷糊糊地就将自己也不知道为什么这么做、是对是错的动作坚持了很多年。 如下内容是根据前几天参加的培训总结的笔记,是我认为比较实用的套路和惯例,迄今为止我尚未拜读过史蒂芬.柯维的大作《高效能人士的七个习惯》。 目录 有鹅才能有金蛋 反射弧里加一环 解决问题三部曲 时间管理的建议 三个实用小惯例 追求卓越模式图 有鹅才能有金蛋 据说一篇有趣的文章都应该以讲故事开头: 一天,一个很穷的农夫在鹅窝里发现了一个金光闪闪的蛋,更让他喜出望外的是这个蛋是纯金的。这之后,农夫每天都可以从鹅窝里拿到一个金蛋。然而,当他日益富有的时候,他也越来越贪婪,以至于没有耐心等待每天只有一个金蛋,他想一次拿到鹅身体里的所有金子,于是他杀了这只鹅,但结果却是什么都没得到。 ——伊索寓言《鹅和金蛋》 这听起来真是一个悲伤的故事啊!但是坑爹的他已经凭白无故拿到好多金蛋可以幸福地生活下半辈子了鹅没了也就没了为毛我连彩票都没中过十块以上的所以人生还是不公平的啊! 好吧寓言应该不是告诉我这样去想问题的,还是听老师的话,这样来理解吧:杀鹅取卵的做法当然不可取,但是想想这样的事情自己却也没少干,随意举几个: 熬夜,少运动——慢性扼杀身体这只鹅 晚上一不留神就熬到夜深,虽然一周打一两次羽毛球但是相对整天地坐着还是太不够…… 偷懒,少学习——慢性扼杀智慧这只鹅 大多数时候还是功利地学习,遇到什么问题只是针对地去解决,事后主动地寻求系统地补充相关知识的时候容易偷懒进度慢…… 死宅,少沟通——慢性扼杀感情这只鹅 跟好多人一样,朋友见面好不容易能交流的时光被消磨在了手机屏幕上…… …… 想要源源不断地收获金蛋,那就要用心去养好自己的几只鹅,让它们心情愉悦精力充沛,远离死亡的威胁。据说这是一份相对合理的每天养身体、智慧和感情这三只鹅的一天二十四小时时间分配图,可以以此为基础根据自己实际情况稍作修改做一份适合自己的作息计划: 反射弧里加一环 一般来讲,我们感觉遇到困难的处境后是这样的: 但是实际上发生的过程是这样的: 这就是为何同一件事情,不同的人会有不同的感受和回应方式的秘密。觉察到中间这一环,就能自由选择以什么样的心情和方式来回应。 解决问题三部曲 定义问题 发现问题 有什么样的现象? 定义问题 我想要什么,如何 XXX? 不能仅停留在发现问题的层面然后就去想对策,让自己用「如何XXX」的句式找出真正的诉求。 分析问题 遇到问题画个圈,弄明白哪些因素是关注就可以的,哪些因素是自己真正能影响的。 关注圈 包括所有关切和担心的因素。比如可能有别人的理解能力、性格等等。 影响圈 包括所有可以影响的因素。比如可能有别人的意愿、自己的沟通技能技巧、第三方的力量和证明材料等等。 主动积极的人会努力扩大影响圈,而消极的人则可能会在困难面前畏缩而导致影响圈里的一些东西被关注圈吞噬。 解决问题 选择影响圈里的项目——计划和实践——总结、确认、重复 时间管理的建议 不能管理时间,便什么都不能管理。 ——彼德.德鲁克 据说努力让手上的事务主要集中在II区域会让你成为一个下面这样的神人: 有远见、有理想 平衡、纪律性强、自制 少有危机、身体健康 工作生活和谐、人际关系良好 良性的团队发展 三个实用小惯例 事情已经如此了,我(们)能做些什么呢? 问题本身不是问题,如何解决才是问题。 与别人沟通时遇到不同的看法,记得用「好,很好,说说看!」和「好,很好,还有吗?」,多问、多听、少说。 追求卓越模式图 好吧扯了半天貌似跟原书关系不大,说的都是些野路子,那么下面干货来了,据说这张图是《高效能人士的七个习惯》这本书的核心。 主要理念: 成长分三个层次:依赖型、独立型和互赖型。依赖心重的人靠别人来完成愿望,独立自主的人自己打天下,互赖的人集思广益以达成功。 依赖型的人能通过主动积极、以终为始、要事第一三个习惯逐渐成长为独立型的人,独立型的人能通过双赢思维、知彼解己、集思广益三个习惯逐渐成长为互赖型的人,不断更新的习惯则是要求能将前面说的六个习惯不断运用,常用常新。 个人成功的要点是自我掌控和自律,公众成功的核心是深入、持久、高效的人际关系。

    2014/10/28 Blog

  2. CSDN 已下载资源自动批量评论脚本

    用 Python 实现自动批量打分评论指定 CSDN 账号内所有下载过待评论的资源。 GitHub 仓库地址:https://github.com/mzlogin/csdncommenter 可通过 pip 安装运行: pip install csdncommenter csdncommenter 背景 CSDN 账号过一段时间就会累积几十个下载过但是未评论打分的资源,虽然现在上传了一些资源供别人下载后基本不愁积分,但是为了可持续发展,还是把评论一下就能顺手拿了的这种积分不客气地收入囊中吧!不过手动一个一个去评论真的很蛋疼……特别是 CSDN 还设了两个评论间隔不能小于 60 秒、刚刚下载的资源十分钟内不能评论的限制,评论几十个就得至少花个几十分钟折腾,所以想想这种耗时、无脑的活还是交给程序来完成吧。 对于这类模拟 HTTP 请求然后可能频繁用到页面解析和正则表达式之类的活,用 C++ 写还是有点蛋疼的,用我那半生不熟的 Python 练练手正合适。 遂在 GitHub 上建了个仓库开工,地址在这里:https://github.com/mzlogin/csdncommenter。 Update 2016/08/10:当前 CSDN 貌似已经取消了评论返积分的规则,我看了下我的得分记录,最近一次评论得分是在 2015/11/15。 分析 使用 Fiddler 把登录 - 到待评论页面 - 评论的完整流程抓了一下,整理程序逻辑大致如下: 注:如下 HTTP 请求均使用同一个 SESSION。 手动输入 CSDN 的用户名和密码。 用 GET 方法从 https://passport.csdn.net/account/login 页面获取 lt、execution 和 _eventId 等参数。 将第 1 步中的用户名和密码,还有第 2 步中得到的参数 POST 给 https://passport.csdn.net/account/login ,从 Response 中判断是否登录成功——我采用的依据是 status_code 为 200 且 Reponse 内容中有 lastLoginIP。 用 GET 方法从 http://download.csdn.net/my/downloads 页面获取已下载资源总页数。从最后一个 pageliststy 的 href 中得到。 根据第 4 步中得到的总页数,根据每个页面 num 拼得 url 为 http://download.csdn.net/my/downloads/num ,使用 GET 方法访问之拿到该页面中所有待评论资源 ID。从所有 class="btn-comment" 的 a 标签的 href 中得到。 在进行第 5 步的过程中,如果 num 为 1 的页面里有 <span class="btn-comment"> 存在,那说明存在 10 分钟以内下载,暂时不能评论的资源,这时循环检查最多 11 次,每次检查完如果发现还需要等待就过一分钟再检查,进到不再需要等待或者超过 11 次为止。 对第 5 步中得到的所有待评论资源 ID 依次进行间隔至少 60S 的打分评论,根据资源的现有评星打分,不对资源评分造成不良影响。根据打出的 1 到 5 星,对应一句英文短句评论。出乎我意料的是评论这一步竟然也是用 GET 就可以做,http://download.csdn.net/index.php/comment/post_comment 后面带上 sourceid、content(评论内容)、rating(打分)和 t(时间戳)参数就可以。评论成功会返回 ({"succ":1}),失败会返回「两次评论需要间隔 60 秒」、「您已经发表过评论」等之类的 msg。 获取资源的现有评星的方法是从 http://download.csdn.net/detail/<资源所有者 ID>/<资源 ID> 页面获取 <span style="width:75px" class="star-yellow"></span> 这一段内容,其中的 75px 表示为五星,如果是 0px 表示为零星,即每加一星增加 15px。 最终运行截图如下: 确认这种方式能有效拿到 CSDN 的分数: 总结 用 Python 干这种类型的活还是很有优势的,requests 和 BeautifulSoup 简直神器啊! 我那点蹩脚的 Python 底子之所以能还比较顺利地把这个流程写下来,实际上也得亏 CSDN 对请求的验证相对较松,比如像我代码里那样写, User-Agent 是带有 Python 字样的,而且很显然不是浏览器在访问,但 CSDN 并未对此作限制。 源码 没有找到从 Github Pages 引用 Github 仓库里的源码的方法,所以把 py 文件放到一个 gist 里了,引用如下: (Gist 前几天被伟大的墙封了,还是直接贴上代码吧。2014/11/5 update) (GitHub 仓库:mzlogin/csdncommenter,现在可以通过 pip 安装使用了 pip install csdncommenter 然后 csdncommenter。2015/10/27 update) # File : csdncommenter.py # Author : Zhuang Ma # E-mail : chumpma(at)gmail.com # Website: https://mazhuang.org # Date : 2016-07-26 import requests from BeautifulSoup import BeautifulSoup import getpass import time import random import re import urllib import traceback class CsdnCommenter(): """Csdn operator""" def __init__(self): self.sess = requests.Session() def login(self): """login and keep session""" username = raw_input('username: ') password = getpass.getpass('password: ') url = 'https://passport.csdn.net/account/login' html = self.getUrlContent(self.sess, url) if html is None: return False soup = BeautifulSoup(html) lt = self.getElementValue(soup, 'name', 'lt') execution = self.getElementValue(soup, 'name', 'execution') _eventId = self.getElementValue(soup, 'name', '_eventId') data = { 'username' : username, 'password' : password, 'lt' : lt, 'execution' : execution, '_eventId' : _eventId } response = None try: response = self.sess.post(url, data) except: # traceback.print_exc() pass return self.isLoginSuccess(response) def autoComment(self): """main handler""" if self.getSourceItems() is False: print('No source can comment!') return print('Total %d source(s) wait for comment.' % len(self.sourceitems)) nhandled = 0 for sourceid in self.sourceitems.keys(): left = len(self.sourceitems) - nhandled sec = random.randrange(61,71) print('Wait %d seconds for start. %s source(s) left.' % (sec, left)) time.sleep(sec) self.comment(sourceid) nhandled += 1 print('Finished!') def getSourceItems(self): """get (sourceid,username) couples wait for comment""" self.sourceitems = dict() pagecount = self.getPageCount() if pagecount == 0: return False print('Pagecount is %d.' % pagecount) pattern = re.compile(r'/detail/([^/]+)/(\d+)#comment') for n in range(1, pagecount + 1): if n == 1: self.waitUncommentableSourceIfNecessary() url = 'http://download.csdn.net/my/downloads/%d' % n html = self.getUrlContent(self.sess, url) if html is None: continue soup = BeautifulSoup(html) sourcelist = soup.findAll('a', attrs={'class' : 'btn-comment'}) if sourcelist is None or len(sourcelist) == 0: continue for source in sourcelist: href = source.get('href', None) if href is not None: rematch = pattern.match(href) if rematch is not None: self.sourceitems[rematch.group(2)] = rematch.group(1) return len(self.sourceitems) > 0 def waitUncommentableSourceIfNecessary(self): """souce cannot comment within 10 minutes after download""" url = 'http://download.csdn.net/my/downloads/1' maxMinutes = 11 for i in range(0, maxMinutes): html = self.getUrlContent(self.sess, url) if html is None: break soup = BeautifulSoup(html) sourcelist = soup.findAll('span', attrs={'class' : 'btn-comment'}) if sourcelist is None or len(sourcelist) == 0: print('None uncommentable source now!') break print('Waiting for uncommentable source count down %d minutes.' % (maxMinutes-i)) time.sleep(60) def getPageCount(self): """get downloaded resources page count""" url = 'http://download.csdn.net/my/downloads' html = self.getUrlContent(self.sess, url) if html is None: print('Get pagecount failed') return 0 soup = BeautifulSoup(html) pagelist = soup.findAll('a', attrs={'class' : 'pageliststy'}) if pagelist is None or len(pagelist) == 0: return 0 lasthref = pagelist[len(pagelist) - 1].get('href', None) if lasthref is None: return 0 return int(filter(str.isdigit, str(lasthref))) def comment(self, sourceid): """comment per source""" print('sourceid %s commenting...' % sourceid) contents = [ 'It just soso, but thank you all the same.', 'Neither good nor bad.', 'It is a nice resource, thanks for share.', 'It is useful for me, thanks.', 'I have looking this for long, thanks.' ] rating = self.getSourceRating(sourceid) print('current rating is %d.' % rating) if rating == 0: # nobody comments rating = 3 content = contents[rating - 1] t = '%d' % (time.time() * 1000) paramsmap = { 'sourceid' : sourceid, 'content' : content, 'rating' : rating, 't' : t } params = urllib.urlencode(paramsmap) url = 'http://download.csdn.net/index.php/comment/post_comment?%s' % params html = self.getUrlContent(self.sess, url) if html is None or html.find('({"succ":1})') == -1: print('sourceid %s comment failed! response is %s.' % (sourceid, html)) else: print('sourceid %s comment succeed!' % sourceid) def getSourceRating(self, sourceid): """get current source rating""" rating = 3 url = 'http://download.csdn.net/detail/%s/%s' % (self.sourceitems[sourceid], sourceid) html = self.getUrlContent(self.sess, url) if html is None: return rating soup = BeautifulSoup(html) ratingspan = soup.findAll('span', attrs={'class': 'star-yellow'}) if ratingspan is None or len(ratingspan) == 0: return rating ratingstyle = ratingspan[0].get('style', None) if ratingstyle is None: return rating rating = int(filter(str.isdigit, str(ratingstyle))) / 15 return rating @staticmethod def getElementValue(soup, element_name, element_value): element = soup.find(attrs={element_name : element_value}) if element is None: return None return element.get('value', None) @staticmethod def isLoginSuccess(response): if response is None or response.status_code != 200: return False return -1 != response.content.find('lastLoginIP') @staticmethod def getUrlContent(session, url): html = None try: response = session.get(url) if response is not None: html = response.text except requests.exceptions.ConnectionError as e: # traceback.print_exc() pass return html def main(): csdn = CsdnCommenter() while csdn.login() is False: print('Login failed! Please try again.') print('Login succeed!') csdn.autoComment() if __name__ == '__main__': main()

    2014/10/12 Python

  3. 获取运行过程中改名的文件的路径

    需求 一个 EXE 在运行过程中(被)改名了,需要准确地获取它的文件名。 尝试 原本以为这是一个非常简单的 CASE,直接用 GetModuleFileName 不就行了吗?结果还真不如我所想。无论程序运行过程中被改名成什么样子,GetModuleFileName 返回的都是 EXE 开始运行时的名字。然后又尝试了 GetProcessImageFileName,也是如此,直到最后找到了 QueryFullProcessImageName。 示例代码 #include <Windows.h> #include <Psapi.h> #include <stdio.h> #pragma comment(lib, "Psapi.lib") void OutputSelfpath() { char szFile[MAX_PATH] = {0}; GetModuleFileName(NULL, szFile, MAX_PATH); printf("GetModuleFileName:\n\r%s\n\n", szFile); memset(szFile, 0, MAX_PATH); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId()); if (!hProcess) { printf("OpenProcess failed!\n"); } else { DWORD dwRet = GetProcessImageFileName(hProcess, szFile, MAX_PATH); if (dwRet) { printf("GetProcessImageFileName:\n\r%s\n\n", szFile); } else { printf("GetProcessImageFileName failed!\n"); } memset(szFile, 0, MAX_PATH); DWORD dwSize = MAX_PATH; if (QueryFullProcessImageName(hProcess, 0, szFile, &dwSize)) { printf("QueryFullProcessImageName:\n\r%s\n\n", szFile); } else { printf("QueryFullProcessImageName failed\n", szFile); } } } int main() { const char* pszFile = "ConsoleTest.exe"; const char* pszNewFile = "ConsoleTest_bak.exe"; remove(pszNewFile); OutputSelfpath(); int nRet = rename(pszFile, pszNewFile); if (0 != nRet) { printf("rename file failed!\n"); } else { printf("################### after rename ###################\n\n"); OutputSelfpath(); } system("pause"); return 0; } 运行结果 思考 现象上讲就是如此了,这几个 API 的本质区别是什么呢?待续。

    2014/10/09 Windows

  4. 眼中的自己

    人的一生最大的敌人是自己,最难看清的也是自己。 借对自己的审视,借别人口中的评价,更好地了解自己,做更好的自己。 优点 可以信任 一般来讲,一件事情如果我应下来,那就会尽力去朝着想要的结果而努力。 热爱阅读 不管有用的没用的,我总是在阅读着,也在进行着一些相应的思考。 缺点 拖延症 任由事情在自己手上堆积,找各种借口不去开始,即使那件事情只需要两分钟就能处理完。 必须症 对自己身上客观存在的一些东西没有无条件地接受;对别人的行为和这个世界的样子存在期待,一旦不符合期待便会焦虑和自我挫败。 含糊症 别人来问我什么问题的时候总是回答得很快,有时候给出的并不是经过思考后最合理最确定的回答,后来反应和发觉过来只好再去补救。其实别人没那么急,略等几秒在脑海里找到确定的答案再答复相对是效率更高的方式,这并不会显得我没有准备和欠思考,实际上它比我立即给出一个不准确的答案而后来再推翻或补充要好。

    2014/09/27 Blog

  5. 如何让你的 EXE/DLL 足够小

    为了节省大量用户下载占用的带宽,又不便使用 P2P 技术,需要做一个尽量小的独立 EXE,这里是对如何让一个简单的 EXE 体积尽量小的部分方法与每一步的实际效果。 初始 DEMO 用 VC++ 生成一个最简单的 Win32 Console Application,调用少量简单的 CRT 函数,因为要独立 EXE,所以使用 /MT,示例代码部分如下,然后 Release 编译看看体积。 #include "stdafx.h" #include <string.h> int _tmain(int argc, _TCHAR* argv[]) { const char* pStr = "hello, world"; char szArray[64] = {0}; strcpy(szArray, pStr); return 0; } 现在大小是 40960 字节。 打开最小体积优化开关 Project - Property - C/C++ - Optimization 将 Optimization 改为 Minimize Size (/O1),重新编译。 现在大小是 40960 字节。 可能是示例程序过于简单,所以此开关并没有产生实际的影响,但是在其它有需求的情况下是可以考虑使用它的,在复杂程序中开优化减小体积还是比较明显的,当然也要提防优化带来的问题。 不生成调试信息 Project - Property - Linker - Debugging 关闭 Generate Debug Info 开关。 现在大小是 40960 字节。 我们可以看到程序大小也未产生变化。这个开关对 Release 文件体积影响较小,在文件较大时也只能压缩几 KB 的大小,而且要承担没有 PDB 后期调试困难的结果,不太建议使用。 去掉 CRT 依赖 这至少需要处理如下几点: 自己指定程序入口点 Project - Property - Linker - Anvanced 在 Entry Point 那里填写你的新入口点函数,比如我写的是NewEntry,然后将 main 函数改成这个名字,比如void NewEntry()。 自己实现用到的 CRT 函数 上面的程序里用到了strcpy,那么我们就自己来实现它,当然你用跟它相同的名字和声明实现一个函数是通不过编译的,VC 会报错error C2169: 'strcpy' : intrinsic function, cannot be defined,要解决这个问题需要关掉一个开关: Project - Property - C/C++ - Optimization 将 Enable Intrinsic Functions 设为 No。 这样修改后的代码如下: #include "stdafx.h" //#include <string.h> extern "C" char * __cdecl strcpy(char * dst, const char * src) { char * cp = dst; while( *cp++ = *src++ ) ; /* Copy src over dst */ return( dst ); } void NewEntry() { const char* pStr = "hello, world"; char szArray[64] = {0}; strcpy(szArray, pStr); } 现在大小是 3584 字节。 这是效果最明显,也是实现起来相对最难的一步,需要我们尽量调用 Windows API 而不是 CRT 函数去做一些事情,如果实在需要 CRT 函数的功能,那就自己写一份吧。这些在代码量大的情况下可能会是一个比较繁琐的过程。 加壳压缩 使用比如 UPX,ASPack 等加壳工具对可执行程序进行压缩。 但是实际发现,在 EXE 文件特别小时,比如像上面已经精简到 3584 字节后,再使用 ASPack 工具压缩会反而令 EXE 文件更大。

    2014/09/13 Windows

  6. 我所理解的生活

    每一次「嘴笨」的背后,都有平时疏于思考的懒惰。——写在最前面。 我想要什么 这个可以分两个方面来讲:物质和精神。 从物质上来讲,我期望能够尽快地拥有世俗眼光里大家认为这个年纪的生活里应该拥有的东西,直白点讲就是车子房子票子妹子(排名不分先后)。 从精神上来讲,我目前比较想的是不久后的某一天,我能惊喜地发现自己已经克服了「必须症」。 急功近利,焦虑不安,是现状。除了特别年幼的那几年,从小到大,一直在学也没学好玩也没玩好的怪圈里挣扎,到如今自认为是循规蹈矩,个性全无。但其实细想一下,我们到底在急什么?大学毕业参加工作才几年时光,就迫不及待地想拥有一切。我们一直在匆忙前行,其实最急迫的可能不是快点往前走,而是偶尔停下来想一想为何这样,调整好前进的方向。 关于爱情 一个人的时候其实是很无畏很能凑合的,是爱让我懂得害怕,害怕失去,害怕在她面前表现得不够好,害怕不能给她好的生活。 两个人免不了要彼此包容,想要在所有事情上都观点相同是不现实的;两个人应该也要能彼此成就,能有一些促进对方做更好的自己的特质。 当然最重要的,我们首先还是自己的自己,然后才会是彼此的彼此,每个人都应该活出一个丰满充实的人生,不应把自己的愉悦与幸福过度地寄托在别人身上,幸福是自己感觉出来的。 任何事情说得太多了就廉价了,比如说爱,比如表白。 关于承诺 一直以来都认为爱情里的承诺是一种很苍白的语言。双方足够信任的时候,不需要承诺,因为会笃定地相信两个人会为了共同的目标去努力。在不得不需要承诺来令对方相信的时候,相信两个人心里都会有一丝悲情和哀伤。 如果必须要承诺,我承诺我会持续不断地努力让自己和爱的人生活得更好。 关于欲望 欲望是无止境的,很多东西,够用舒适就行,在这样的前提下,容易满足的人更容易得到更多的幸福感吧。

    2014/08/30 Blog

  7. 摄影教训总结

    2014 年 8 月 杭州 以人为主体没错,但是要有远有近,拍出来全是脸和上半身也不好。 偶尔兼顾景色,不是所有情况下虚化背景都是加分。 一般情况下色彩鲜艳的衣服拍出来会更好看。 人像镜头不是万能的,大光圈容易虚焦,取景范围影响构图,这些都需要更好地考虑。 2014 年 6 月 武汉 照顾好拍摄对象的心情,她开心和配合了,才容易出好片。 如果她真的不愿意拍,就不拍了吧,否则出来基本也是废的。 蓝天白云绿水真的很容易出好片,但是阴天加浑浊的长江边还是不要轻易挑战了。 2014 年 3 月 武汉 气氛要活泼一点,不然模特摆拍容易表情僵硬。 光圈优先在室外光线好的情况下容易过曝。 只挑拍得最好的片发给模特看,如果没有出好片,宁愿不发。

    2014/08/26 Blog

  8. TaobaoProtectSE.dll 注入引起的死锁分析

    案例背景 一个以前运行良好的 Windows 程序,在添加了少量功能之后,在若干台测试机中的某一台上运行后一直得不到预期结果,并且能比较高机率地复现。排错过程如下: 听完测试同学的描述后,以为是程序执行完了但是没有结果不对,于是以为是因为什么原因提前退出了,就复查代码中的分支逻辑,发现必经的路径中都有没有执行到的,而且写了一个小例过去发现必经路径能正常执行,感觉很诡异也很纳闷。 偶然发现是进程一直卡在后台没有正确执行也没有退出(之前寻找原因的过程中思路有点被测试同学的描述带跑了,导致居然是「偶然」才发现的,这个要反思)。 用 WinDbg 工具 Attach 卡死进程并分析之。 WinDbg 分析过程 使用 ~*kv 查看完整堆栈如下: 0:002> ~*kv 0 Id: 18a4.20f0 Suspend: 1 Teb: 7efdd000 Unfrozen ChildEBP RetAddr Args to Child 0037e9e0 77518e44 0000012c 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15 (FPO: [3,0,0]) 0037ea44 77518d28 00000000 00000000 0037eaac ntdll!RtlpWaitOnCriticalSection+0x13e (FPO: [Non-Fpo]) 0037ea6c 775102c9 775e20c0 768b6af6 756cdc60 ntdll!RtlEnterCriticalSection+0x150 (FPO: [Non-Fpo]) 0037eb08 77510202 766c0000 0037eb44 00000000 ntdll!LdrGetProcedureAddressEx+0x159 (FPO: [Non-Fpo]) 0037eb24 768e1e59 766c0000 0037eb44 00000000 ntdll!LdrGetProcedureAddress+0x18 (FPO: [Non-Fpo]) 0037eb4c 75393962 766c0000 756cdc60 0037ed08 KERNELBASE!GetProcAddress+0x44 (FPO: [Non-Fpo]) 0037eb94 753939a7 766c0000 756da27c 00000002 SHELL32!__delayLoadHelper2+0xe9 (FPO: [Non-Fpo]) 0037ec30 7539439a 0037f0b8 00020019 0037ed08 SHELL32!_tailMerge_ole32_dll+0xd 0037ed88 7539498a 0037f0b8 0037ee1c 87ee6cf8 SHELL32!kfapi::CFolderDefinitionStorage::_LoadRegistry+0x44 (FPO: [Non-Fpo]) 0037ef04 7539483e 0037f0b8 0037efc0 0037efbb SHELL32!kfapi::CFolderDefinitionStorage::Load+0x49 (FPO: [Non-Fpo]) 0037f03c 753934c4 0037f0b8 0037f078 87ee7308 SHELL32!kfapi::CFolderDefinitionCache::Load+0x7b (FPO: [Non-Fpo]) 0037f0f4 75323d49 0037f2ec 20004600 007a8fa8 SHELL32!kfapi::CFolderPathResolver::GetPath+0x96 (FPO: [Non-Fpo]) 0037f1c4 753bcbfa 0037f2ec 00000000 20004600 SHELL32!kfapi::CFolderCache::GetPath+0xd7 (FPO: [Non-Fpo]) 0037f278 753bcb36 0037f2ec 20004600 00000000 SHELL32!kfapi::CKFFacade::GetFolderPath+0xd5 (FPO: [Non-Fpo]) 0037f2a0 753955d5 0037f2ec 20004600 00000000 SHELL32!SHGetKnownFolderPath_Internal+0x96 (FPO: [Non-Fpo]) 0037f2bc 75395714 0037f2ec 00000000 00000000 SHELL32!SHGetFolderPathEx+0x30 (FPO: [Non-Fpo]) *** ERROR: Module load completed but symbols could not be loaded for C:\Users\mazhuang\Desktop\ProblemProgram.exe 0037f300 00c16d30 00000000 0000002e 00000000 SHELL32!SHGetFolderPathW+0x114 (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 0037f448 00c122ab 0037f508 00000104 00c92858 ProblemProgram+0x6d30 0037f4a4 77519dec ffffffff 009915c0 00000000 ProblemProgram+0x22ab 0037f4dc 01000000 00000005 00000007 0044005c ntdll!RtlDecodePointer+0x17 (FPO: [Non-Fpo]) 0037f4e4 00000000 0044005c 00760065 00630069 ProblemProgram+0x3f0000 1 Id: 18a4.1e84 Suspend: 1 Teb: 7efda000 Unfrozen ChildEBP RetAddr Args to Child 02a8e270 77518e44 0000014c 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15 (FPO: [3,0,0]) 02a8e2d4 77518d28 00000000 00000000 02a8e4b8 ntdll!RtlpWaitOnCriticalSection+0x13e (FPO: [Non-Fpo]) 02a8e2fc 753bc3b5 007a8fcc 007a8fa8 02a8e43c ntdll!RtlEnterCriticalSection+0x150 (FPO: [Non-Fpo]) 02a8e30c 753bc64d 007a8fcc 857167c0 756daf98 SHELL32!kfapi::CCriticalSectionLock::CCriticalSectionLock+0x14 (FPO: [Non-Fpo]) 02a8e43c 753934c4 02a8e4b8 02a8e478 85716708 SHELL32!kfapi::CFolderDefinitionCache::Load+0x2c (FPO: [Non-Fpo]) 02a8e4f4 75394a87 02a8e6ec 20008000 007a8fa8 SHELL32!kfapi::CFolderPathResolver::GetPath+0x96 (FPO: [Non-Fpo]) 02a8e5c4 753bcbfa 02a8e6ec 00000000 20008000 SHELL32!kfapi::CFolderCache::GetPath+0x23d (FPO: [Non-Fpo]) 02a8e678 753bcb36 02a8e6ec 20008000 00000000 SHELL32!kfapi::CKFFacade::GetFolderPath+0xd5 (FPO: [Non-Fpo]) 02a8e6a0 753955d5 02a8e6ec 20008000 00000000 SHELL32!SHGetKnownFolderPath_Internal+0x96 (FPO: [Non-Fpo]) 02a8e6bc 75395714 02a8e6ec 00000000 00000000 SHELL32!SHGetFolderPathEx+0x30 (FPO: [Non-Fpo]) 02a8e700 754277a7 00000000 0000801a 00000000 SHELL32!SHGetFolderPathW+0x114 (FPO: [Non-Fpo]) *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Users\mazhuang\AppData\Roaming\TaobaoProtect\TaobaoProtectSE.dll - 02a8e930 663261e7 00000000 0000801a 00000000 SHELL32!SHGetFolderPathA+0x3e (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 02a8ee98 66324a65 775138aa 86d8babf 02a8f364 TaobaoProtectSE+0x61e7 02a8f220 6633f57e 66320000 86d8a657 00000001 TaobaoProtectSE+0x4a65 02a8f240 66356dc5 66320000 00000001 00000000 TaobaoProtectSE+0x1f57e 02a8f280 66356d4c 66320000 00000001 00000000 TaobaoProtectSE!UninstallMonitor+0x176e5 02a8f294 775199a0 66320000 00000001 00000000 TaobaoProtectSE!UninstallMonitor+0x1766c 02a8f2b4 7751d939 663a9650 66320000 00000001 ntdll!LdrpCallInitRoutine+0x14 02a8f3a8 7751d7fc 00000000 741474ea 00000000 ntdll!LdrpRunInitializeRoutines+0x26f (FPO: [Non-Fpo]) 02a8f514 7751c558 02a8f578 02a8f540 00000000 ntdll!LdrpLoadDll+0x4d1 (FPO: [Non-Fpo]) 02a8f54c 768e2ca2 02a8f540 02a8f590 02a8f578 ntdll!LdrLoadDll+0xaa (FPO: [Non-Fpo]) 02a8f588 75f7aac3 00000000 00000000 007a904c KERNELBASE!LoadLibraryExW+0x1f1 (FPO: [Non-Fpo]) 02a8f6c8 774f010a 02a8f6e0 00000000 02a8f7b0 USER32!__ClientLoadLibrary+0x66 (FPO: [Non-Fpo]) 02a8f6dc 00000000 00000090 00000000 0c580a20 ntdll!KiUserCallbackDispatcher+0x2e (FPO: [0,0,0]) 程序就两个线程且都是卡在 ntdll!ZwWaitForSingleObject,很有可能是发生了死锁。使用 !cs 和两个线程堆栈中 ntdll!RtlEnterCriticalSection 这一行的第一个参数来看它们等待的临界区的情况: 0:002> !cs 775e20c0 ----------------------------------------- Critical section = 0x775e20c0 (ntdll!LdrpLoaderLock+0x0) DebugInfo = 0x775e4380 LOCKED LockCount = 0x1 WaiterWoken = No OwningThread = 0x00001e84 RecursionCount = 0x1 LockSemaphore = 0x12C SpinCount = 0x00000000 0:002> !cs 007a8fcc ----------------------------------------- Critical section = 0x007a8fcc (+0x7A8FCC) DebugInfo = 0x00796af8 LOCKED LockCount = 0x1 WaiterWoken = No OwningThread = 0x000020f0 RecursionCount = 0x1 LockSemaphore = 0x14C SpinCount = 0x00000000 从它们各自的 OwningThread 可以得出这两个线程等待的临界区分别被对方占用,即它们在相互等待,典型的死锁。 可以看到 1e84 线程的堆栈中出现了 TaobaoProtectSE,很显然程序被注入了一个 DLL,并且死锁与这个注入的 DLL 有关。 我留意到 20f0 线程堆栈中有 KERNELBASE!GetProcAddress,看一下它正在尝试找到什么函数的入口点: 0:002> da 756cdc60 756cdc60 "StringFromGUID2" 这是 ole32.dll 中的一个函数,看起来应该是在 SHGetFolderPathW 函数中对传入的 CSIDL 参数进行转换的。 两个堆栈中都出现了 SHELL32!SHGetFolderPathW,是否这个函数执行时需要占用一个临界区呢?1e84 线程中比较靠近栈顶的 SHELL32!kfapi::CCriticalSectionLock::CCriticalSectionLock 这一帧应该就能给出肯定的答案了。 看了一下任务管理器中的进程,推测是 TaobaoProtect.exe 进程执行的注入,对它用 WinDbg 下了一些断点调了一下,发现是它使用 SetWindowsHookExW 下了全局的 WH_GETMESSAGE 和 WH_MOUSE_LL 钩子。 Breakpoint 0 hit *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Users\mazhuang\AppData\Roaming\TaobaoProtect\TaobaoProtectSE.dll - eax=00000000 ebx=007a1c78 ecx=d53fa1d7 edx=00000094 esi=6633f680 edi=76f11222 eip=75f87603 esp=0a10f8cc ebp=0a10ff1c iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 USER32!SetWindowsHookExW: 75f87603 8bff mov edi,edi 0:020> kv ChildEBP RetAddr Args to Child 0a10f8c8 6633f69d 00000003 6633f660 66320000 USER32!SetWindowsHookExW (FPO: [Non-Fpo]) WARNING: Stack unwind information not available. Following frames may be wrong. 0a10ff1c 0044e75c df2f5e93 00000000 00a7bc60 TaobaoProtectSE!InstallMonitor+0x1d 0a10ff44 005f6911 007a1c78 df2f5eab 00000000 image00400000+0x4e75c 0a10ff7c 005f6a39 00000000 0a10ff94 76f1338a image00400000+0x1f6911 0a10ff88 76f1338a 00a7bc60 0a10ffd4 77519f72 image00400000+0x1f6a39 0a10ff94 77519f72 00a7bc60 7f47b808 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo]) 0a10ffd4 77519f45 005f69bd 00a7bc60 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo]) 0a10ffec 00000000 005f69bd 00a7bc60 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo]) USER32!SetWindowsHookExW 这一帧的第一个参数 00000003 对应 WH_GETMESSAGE。 死锁情景分析 结合自己了解到的一些知识和搜索引擎里查到的,得出死锁发生时的场景如下: 简化后的程序代码示例 UINT WINAPI ThreadProc(PVOID pParam) { MSG msg; while (GetMessage(&msg, 0, 0, 0)) { ... } ... } int _tmain(int argc, _TCHAR* argv[]) { UINT nThreadId = 0; HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, NULL, 0, &nThreadId); ... ::SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, szPath); ... } 主线程在启动辅助线程后,调用了 SHGetFolderPath,在此函数的执行过程中需要进入一个临界区,它成功进入了此临界区。而随后解析 CSIDL 需要用到 ole32.dll 中的 StringFromGUID2 函数,此时进程还未完成找到函数入口点的工作,所以需要调用 LoadLibraryEx 或 GetProcAddress 去获取此函数的入口点,需要申请 LoaderLock。 (参考 http://technet.microsoft.com/zh-cn/magazine/ms172219(VS.80).aspx 可知 LoadLibrary、GetProcAddress、FreeLibrary 和 GetModuleHandle 等函数是需要申请 LoaderLock 的。) 辅助线程在调用 GetMessage 时被注入 TaobaoProtectSE.dll,已获取 LoaderLock 去 LoadLibraryEx 该 DLL 和执行 DLL 的入口函数,由于此 DLL 的入口函数中直接或间接调用了 SHGetFolderPathW ,同主线程中的调用过程,它需要进入主线程占用的临界区去完成调用。 即主线程进入了临界区,要申请 LoaderLock;辅助线程获取了 LoaderLock,要申请进入临界区,形成死锁。 对比分析与解决方案 以前的程序之所以在新程序出问题的测试机上没有问题,是因为这个调用了 GetMessage 的辅助线程是新加的,以前的程序没有调用过 GetMessage ,所以没有被注入 TaobaoProtectSE.dll。 解决方案: 让 SHGetFolderPathW 的首次调用在起辅助线程之前就完成,再次调用它时应该不需要再寻找函数入口点,不需要再申请 LoaderLock 即可消除死锁风险。在起辅助线程前后都有对 SHGetFolderPathW 的调用经多次试验未再发生死锁现象。 添加对 LoadLibrary 系列函数的 HOOK(即对 LoadLibraryA/LoadLibraryW/LoadLibraryExA/LoadLibraryExW 都进行 HOOK),拒绝 TaobaoProtectSE.dll 注入。已验证可生效。 其它设想: 将 GetMessage 替换为 PeekMessage,结果发现当 PeekMessage 返回 TRUE 的时候仍然被注入。查找 MSDN 看到如下解释: GetMsgProc callback function An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function whenever the GetMessage or PeekMessage function has retrieved a message from an application message queue. Before returning the retrieved message to the caller, the system passes the message to the hook procedure.

    2014/08/01 WinDbg

  9. 定制 Fiddler 之将请求发往另一服务器

    需求 对 Fiddler 抓取的某个特定 SESSION 能在必要时手动操作发往另一个服务器。 设想 在 SESSION 上点击右键弹出的菜单中添加一项,让它对应的响应事件来完成此操作。而联想到 Fiddler 的 Composer 功能能够将某条 SESSION 按自己的需要修改后重新发出,那利用 Composer 来做应该是比较容易实现且便捷的方式。 实现 对 Fiddler 的扩展比较方便的是使用 FiddlerScript,修改 CustomRules.js 来做。比如希望在 hostname 为www.mazhuang.org的 SESSION 上右键后利用自己添加的菜单项将此 SESSION 的 hostname 修改为mazhuang.org后重新发送请求,最终在 CustomRules.js 文件中添加了如下代码即可。 添加方法: 启动 Fiddler > 选择菜单 Rules > 选择菜单项 Customize Rules… > 将如下代码粘贴在OnDetach函数后面 > 保存 注:发现将下面的函数放在OnDetach函数前自己添加的菜单项就不是第一项,而放在OnDetach后就是第一项了,这个很奇怪,未想到合理原因。 public static ContextAction("发送到 mazhuang.org") function DoSend2RootDomain(oSessions: Fiddler.Session[]){ var oS: Session = FiddlerApplication.UI.GetFirstSelectedSession(); if (null == oS) return; if (oS.HostnameIs("www.mazhuang.org")) { oS.hostname = "mazhuang.org"; FiddlerApplication.DoComposeByCloning(oS); } else { MessageBox.Show("不是发往 www.mazhuang.org 的请求"); } } 然后就能看到效果了,在 hostname 为www.mazhuang.org的 SESSION 上右键,点击刚刚我们自己添加的「发送到 mazhuang.org」菜单项,会发现 Fiddler 界面右边的 Composer 标签已打开,然后 hostname 已经替换为mazhuang.org,这时再手动点击 Execute 按钮即可将更改 hostname 后的请求重新发出。 缺陷 当前做法有如下缺陷,尚未想到好办法解决: 会破坏原 SESSION,即将原 SESSION 的 hostname 也替换为了mazhuang.org。 一次操作需要点选右键菜单项后再点击一次 Composer 窗口中的 Execute 按钮才能完成,比较理想的状况是点选右键菜单后即完成替换 hostname 且重新发出请求。 附注 我使用的完整最新的 CustomRules.js 文件我上传到了一个 Gist 里,详见:https://gist.github.com/mzlogin/3c5f9781c5bedff3fcfb,如果想直接使用可以复制脚本内容后放置到「我的文档 /Fiddler 2/Scripts/CustomRules.js」,也可以在此目录下使用 git 抓取我的最新定制 js 文件。

    2014/07/20 Fiddler

  10. 定制 Fiddler 之抓获 WinHTTP 请求

    背景 发现使用 Fiddler 进行抓包时有一部分请求总是没抓到,查看了一下源代码,发现使用 WinINET 这套 API 发送的请求都能正常抓到,而使用 WinHTTP 这套 API 发送的请求都没有抓到,遂搜索了一下,果然前人们早已给出答案,解决方案原文可以参看 Fiddler 作者 Eric Lawrence 大神的一篇博客 Using Fiddler with WinHTTP,博客里表示 Fiddler 对各种 HTTP(s) stacks 都是能支持的,只是默认启动时只是接管了 WinINET 代理设置。 Eric 的那篇博客里已经列出了相关的方法和代码,本文只是对其略做改进,让同一段代码可以适配不同的 Windows 版本。 分析 我们需要让 Fiddler 抓取 WinHTTP 的包时,要做的就是让 WinHTTP 的代理设置改为与 WinINET 一致,因为 WinINET 在 Fiddler 启动后使用 Fiddler 作为代理。这些通过 Windows 自带命令就可以做到: 在 XP 下: proxycfg -u 在 Win7 下(使用管理员权限的命令行): netsh winhttp import proxy ie 注:在 Win7 64 位系统下需要将 System32 目录和 SysWOW64 目录下的 netsh 命令各执行一次,下方将给出的脚本已覆盖这种情况。 但是如果使用频繁,每次都还要去手动敲命令行还是挺痛苦的,作为能偷懒的地方绝不多放过的少年,一劳永逸的方法当然是让它随 Fiddler 的启动与关闭自动执行这些命令(当然这就是 Eric 的博客里讲述的方法)。 实现 这可以通过修改 CustomRules.js 实现(如果想对 Fiddler 的扩展机制进行深入了解可以去参阅 Fiddler 官网的文档)。 操作方法: 打开 Fiddler > 点击菜单 Rules > 点击 Customize Rules… 然后就打开了 CustomRules.js 文件,寻找到OnAttach与OnDetach函数,可以将 Fiddler 启动后与关闭前需要定制的一些自动动作分别填写在它们里头,我们为实现让 Fiddler 能抓取 WinHTTP 发送的请求的目的而修改后的代码如下,添加了UpdateWinHTTPSettings函数,在OnAttach和OnDetach里添加了对它的调用,修改完后保存即可生效。 static function OnAttach() { UpdateWinHTTPSettings(); } static function OnDetach() { UpdateWinHTTPSettings(); } static function UpdateWinHTTPSettings() { var oPSI: System.Diagnostics.ProcessStartInfo = new System.Diagnostics.ProcessStartInfo(); var os : OperatingSystem = Environment.OSVersion; if (os.Version.Major >= 6) { oPSI.UseShellExecute = true; oPSI.FileName = "netsh.exe"; oPSI.Verb = "runas"; oPSI.Arguments = "winhttp import proxy ie"; System.Diagnostics.Process.Start(oPSI); // Re-run 32bit version oPSI.FileName = oPSI.FileName = Environment.SystemDirectory.Replace("system32", "syswow64") + "\\netsh.exe"; if (System.IO.File.Exists(oPSI.FileName)) { System.Diagnostics.Process.Start(oPSI); } } else { oPSI.UseShellExecute = true; oPSI.FileName = "proxycfg.exe"; oPSI.Verb = "open"; oPSI.Arguments = "-u"; System.Diagnostics.Process.Start(oPSI); } } UpdateWinHTTPSettings函数里做的事情其实很简单,就是使用管理员权限执行文章前面说到的命令。 附注 我使用的完整最新的 CustomRules.js 文件我上传到了一个 Gist 里,详见:https://gist.github.com/mzlogin/3c5f9781c5bedff3fcfb,如果想直接使用可以复制脚本内容后放置到「我的文档 /Fiddler 2/Scripts/CustomRules.js」,也可以在此目录下使用 git 抓取我的最新定制 js 文件。

    2014/07/19 Fiddler