利用这个插件,可以方便的将您编写的文章发布到WordPress里面。 登录到您的WordPress系统管理界面,点击设置->撰写,然后选择“启用 WordPress、Movable Type、MetaWeblog 和 Blogger 的 XML-RPC 发布协议。” 下载本文附件,然后解压缩到任意目录,例如e:\caplugins\ 然后点击开始菜单,运行,输入 regsvr32 “e:\caplugins\BlogPublisher.dll” 注册这个插件。 如果您用的是Vista,而且开启了UAC功能,您需要这样处理: 在资源管理器里面打开文件夹c:\windows\system32,找到cmd.exe这个文件,然后右键点击,选择“以管理员身份运行”。然后在命令行里面,输入 regsvr32 “e:\caplugins\BlogPublisher.dll” 来完成注册。 在CyberArticle里面,选中一篇文章,在节点上面点击右键,菜单中选择“高级->Publish to your Blog” 将会出现一个对话框: Server服务器信息: Action操作: Options选项: 设定好信息后,点击确定按钮,就会将您的文章发布到Blog系统上面。如果中间出错,将会报告错误原因。您也可以查看CyberArticle Log文件,来获得详细信息。 该插件会同时将CyberArticle文章的附件发布到Blog上面。同时在文章末尾增加附件列表。 目前只能发布文章里面的图片,其他资源,例如Flash,音乐,视频,CSS等等,不会发布。 文章里面的图片,重复上传或者更新文章的时候,可能无法覆盖原有的图片。 相对于Live Write的优势:编辑文章的时候,可以任意粘贴图片,粘贴HTML里面的图片等等。文章在本地也有备份。
CyberArticle 插件: WordPress 发布插件
1、设置WordPress系统
2、下载并安装CyberArticle Blog 发布插件
3、运行CyberArticle,发布文章

4、注意
Attachments
无标题
2009/04/16VC6常用的库
2009/03/29vc6快10岁了,呵呵~~蛮经典的东西。 以下SDK和库是我自己收集的,绝大部分我都使用过,最重要的是能在VC6下使用。它们之间各自有各自的功能,不需要比较,除非是相同类型的库,例如XML解析器,我才比较一下,排名也不分先后,并且描述的简略不代表个人的感情色情。^.V,很多库我都喜欢,但我只是简单说两句。例如MFC,STL,ICE等等。希望大家的开发效率能提高不少。有些库或者SDK没有罗列其中,大家可以补上。 Windows server 2003 r2 SDK(最新的Windows SDK是Vista版的) 提供最新操作系统的API接口,支持Windows2003r2以及以前的系统,如果想使用一些平台特性,这开发包是必备的。 http://www.microsoft.com/downloads/info.aspx?na=22&p=22&SrcDisplayLang=en&SrcCategoryId=&SrcFamilyId=&u=%2fdownloads%2fdetails.aspx%3fFamilyID%3de15438ac-60be-41bd-aa14-7f1e0f19ca0d%26DisplayLang%3den SDK属于Visual C++的一部分,但其自带的版本较老,已经不适合一些产品了,例如WinXP等.该SDK包含以下MS产品的SDK: Windows,Office,Windows Script(这个应该算是个产品吧..WScript/CScript),netmeeting,IIS, Internet Explorer,MS XML,GDI+,Windows Media Services,DirectShow… 包含以下的程序库:ATL,MFC,OpenGL… 更多信息请查看SDK或者MSDN自带的帮助目录. netmeeting SDK 想将远程桌面,多人会议,视频,文件传送,电子白板功能嵌入到你的程序或者网站中吗?用它就没错了. 内含在Windows server 2003 r2 SDK Internet Explorer SDK 可以用它来解析网页,从而开发出自己特别的需求的“新浏览器”,也可以扩展IE。遨游,TT等外壳浏览器就属于这类应用。QZONE也属于,新版本的QZONE是采用自动化的方式去扩展。 内含在Windows SDK里。 WMEncoderSDK Windows Media编码器的开发包,可以从影像捕捉设备或桌面画面录制,亦提供文件格式转换的功能。 ———— 是一套容易使用,而且功能强大的软件,提供使用者自行录制影像的功能,可以从影像捕捉设备或桌面画面录制,亦提供文件格式转换的功能。主要的特色在于容易使用、高品质编码、增强的可程序化与管理,特点为:新的使用者界面和向导,更容易设定与制作影片,用来提供网络现场播放或需求播放,并支持多重来源,可以立即切换来源,并可监视编码程序进行时的资料,如影像大小、资料流量等等。新的编码能力,支持de-interlacing、inverse telecine和屏幕捕捉,能有更好的输出品质,能从320*240*60fps到640*480*30fps,捕捉文件最大可到30GB,支持的捕捉设备包括Winnov、ATI、Hauppauge,以及USB视讯摄影机等。Windows Media Encoder SDK提供网站开发者全自动的编码控制,可从网络(LAN)远端控制,或透过API存取或ASP控制 —————- http://www.microsoft.com/downloads/details.aspx?familyid=5691BA02-E496-465A-BBA9-B2F1182CDF24&displaylang=en WMPlayerSDK 为Windows Media Player开发插件或者调用其组件的开发包。 http://www.microsoft.com/downloads/details.aspx?FamilyID=e43cbe59-678a-458a-86a7-ff1716fad02f&DisplayLang=en detours Microsoft自己出的一个PE镜像操作包,可以轻松实现API Hook,修改IAT等。 http://research.microsoft.com/research/downloads/Details/10E5D78C-592C-419D-A53E-BAE8DBD81801/Details.aspx WTL(Windows Template Library) 一个基于模板技术、简洁而又完整的界面库,能生成小巧的应用程序,厌倦了庞大的MFC,可以考虑使用它来开发界面,除了对界面提供支持,还提供了一系列的辅助类,例如:CString,CFindFile等。8.0支持WinCE,以及Vista的特性。 http://www.microsoft.com/downloads/details.aspx?FamilyID=e5ba5ba4-6e6b-462a-b24c-61115e846f0c&DisplayLang=en DirectX SDK 能出色地完成高速的实时动画渲染、交互式音乐与环境音效、高效多媒体数据处理等任务。Windows下游戏开发一般使用它。 http://www.microsoft.com/downloads/details.aspx?familyid=4b78a58a-e672-4b83-a28e-72b5e93bd60a&displaylang=en DDK/IFS DDK(Windows Driver Development Kit) 用于开发Windows驱动程序的开发包,装了它VC也能开发驱动程序,不过推荐使用DDK带的build工具进行编译。IFS DDK可以开发文件系统驱动。 http://www.microsoft.com/whdc/devtools/ddk/default.mspx MS CHART 可以在程序里面画出专业的柱状图,曲线图等专业的统计图形。 内含在VB或者office的安装包里。 ATL 用于开发COM的一个框架,有了它,写COM就轻松很多了。除了对COM的支持,还提供了CImage(GDI+的包装类,很好用)、CRegKey(注册表的支持)、CAtlRegExp(正则表达式)等。 VC自带或者包含在Windows SDK中 GDI+ SDK GDI+是Microsoft的新的图形编程接口,具有简单、易用等特性。支持多种图象格式,不必再为jpg,gif等格式解码而发愁。对比GDI,有以下新特性,支持渐变画刷、对立的路径对象、矩阵对象、多种图片格式等。WinXP以及以上系统自带Gdi+所需的DLL。 包含在新版Visual Studio或者包含在Windows SDK中 CxImage 一套图象操作代码,支持多种格式:包括bmp,jpg,png,gif(静态和动态都支持),wbmp,tif,wmf,pcx,tga,ico等.基于GDI的操作而不是GDI+.并提供了一系列的算法,例如缩放,旋转,灰度等等. http://www.xdp.it MFC 一个非常老(比VC6还老)而且优秀的程序框架,是对Windows API源码级的封装,有不少的优秀软件就是用它写的。 包含在Visual Studio中 Xtreme ToolkitPro/BCGControlBar Professional 非常优秀MFC扩展库,用于界面开发,它们提供了仿Office,Visual Studio等MS产品外观的控件. Xtreme有免费版本CJLibrary http://www.codejock.com/ BCG在VS2008里是MFC的一部分了,http://www.bcgsoft.com/ WFC(Win32 Foundation Classes) 一个MFC扩展库,封装了那些MFC没有封装的Win32 API..例如:CDesktop,CMixer,CRegistry等等 http://www.codeproject.com/library/wfc.asp Microsoft Speech SDK 文本朗读和语音识别的开发包。也支持中文发音。 http://www.microsoft.com/speech http://www.microsoft.com/downloads/details.aspx?FamilyID=5e86ec97-40a7-453f-b0ee-6583171b4530&DisplayLang=en MS Agent WinXP搜索里的那只黄色小狗或者Office2003里面的助手就是MS Agent,用这个开发包就可以控制他们。 包含在Visual Studio或者包含在Windows SDK中 MS XML/tinyXML 用于解析XML文件的开发包。 MS XML功能强大,对中文有完美的支持. tinyXML体积小,带源代码. (其它XML解析器都不怎么好,IBM的XML4C功能虽强,可是它的DLL有12M那么大,Xerces c++不能支持中文,Libxml要支持中文的话需要自己写转换函数) MS XML:http://www.microsoft.com/downloads/details.aspx?FamilyID=993c0bcf-3bcf-4009-be21-27e85e1857b1&DisplayLang=en tinyXML:www.sourceforge.net/projects/tinyxml OpenGL 是个专业的3D程序接口,是一个功能强大,调用方便的底层3D图形库。OpenGL是个与硬件无关的软件接口,可以在不同的平台工作。 包含在Visual Studio或者包含在Windows SDK中 STL 非常优秀的C++标准库,提供数据容器以及通用算法等的C++库. 包含在Visual Studio Boost 一套开放源代码、高度可移植的C++库,提供数值计算、泛型编程、元编程、平台API等支持。常用的有Regex,Lambda,smart_ptr等等 http://www.boost.org WinPcap 最常用的就是用它来捕获网络封包。很多网络程序,以前用过的一个电信的拨号器,Ethereal等都是使用这个。 http://winpcap.polito.it zLib 一个开源的数据无损压缩库.最方便的是它可以压缩内存缓冲,而且速度快,很多网络游戏都使用了它压缩数据包. http://www.gzip.org/zlib/ Xvid/Divx 视频编码/解码库.(Divx是个商业产品,Xvid是个开源项目) www.xvid.org ACE/ICE ACE全称adaptive communication enviroment,是一套C++的通信库。它提供了socket/threading/memory management等多种系统调用的面对对象的wrapper,使C++通信软件开发更加简单。 ICE(Internet Communications Engine)一种现代的面向对象中间件,可用于替代像CORBA或COM/DCOM/COM+这样的中间件,特点是开发简易,运行效率高。可以开发出电信级别的应用。 ACE:http://www.cs.wustl.edu/~schmidt/ACE.html ICE:http://www.zeroc.com/ crypto++ 实现了各种公开密钥算法、对称加密算法、数字签名算法、信息摘要算法以及其相关的其它密码算法等等.其实我只用里面的md5,crc32和aes. http://sourceforge.net/projects/cryptopp WxWindows (跨平台的GUI库) 类层次极像MFC,通过多年的开发也是一个日趋完善的GUI库,完全开放源代码的。 http://www.wxwindows.org/ blitz (高效率的数值计算函数库) Blitz++ 是一个高效率的数值计算函数库,它的设计目的是希望建立一套既具像C++ 一样方便,同时又比Fortran速度更快的数值计算环境。 http://folk.uio.no/patricg/blitz/html/index.html
.net开发
2009/03/29http://hufeiyan79.gjjblog.com/archives/777702/
原文地址:http://aspalliance.com/1096_Debugging_JavaScript_in_Visual_Studio
原文发布日期:2006.12.19
摘要
一个好的应用程序在把所有的输入项提交至服务器处理之前都要先通过验证,所以客户端验证是必需的。几乎所有的浏览器都支持的JavaScript便可以非常好的完成这个任务。
作者:Rajendra Kumar Sahu
文章内容
介绍
配置
结论
介绍
事实上,web应用程序相对于桌面应用程序来说,升级起来更加方便。如我们从应用程序的立场来看这两种技术的发展,无疑,web应用程序已经比桌面应用程序更流行了。web应用程序的设计师不同于桌面应用程序的设计师,web页是由静态的html标记,图片,脚本标记和JavaScript构成的。web页与web服务端相结合的,通过浏览器把请求交付给服务端进行处理。在今天,有很多种不同的web服务器。流行的有IIS,WebSphere,Weblogic,Apache等。web服务器是用来处理脚本的,而处理这些脚本的是一些基本的类库。当一个页从浏览器发送请求到服务端时并不会转换html标记,解释和解析这些html标记的工作是由浏览器负责的。
web页在今天已经变得越来越有互动性了,它经常收集用户的输入并且发送这些信息到服务端去处理。所以,在页面中验证每一个输入便成了开发者工作的一部分。web页端的验证已经是必须要做的工作了。如果不做客户端验证,那么就可能会有一些错误的请求,而这些错误的请求将导致web应用程序抛出一个错误信息甚至崩溃。除了在服务端进行输入验证外,客户端验证也是必需的。现在有一些流行的脚本就可以用于客户端验证,JavaScript和VBScript就是其中的两个。没有专门应付web端验证的脚本使得开发者陷入了一个窘迫的境地。当今时代,用户更喜欢在客户端做更多事情,而只有在必须的时候才交给服务端处理。
这种技术完全是在客户端进行处理,它不是ajax(Asynchronous Java and XML)。我在一些场合用到了ajax,它确实是很强大而且不同JavaScript的,但在ajax中仍然会需要用到大量的JavaScript。开发人员不得不把大量的逻辑代码写成JavaScirpt,而随着JavaScript使用的大量增加,越来越多的时间就会用于调试和修改这些JavaScript。微软的Visual Studio IDE可以让开发人员非常容易的写代码,而且调试也相对于传统的asp应用程序简单得多。放下VS IDE的一些新特性不说,很多人发现调试JavaScript很不方便。但是现在你可以通过一些配置使调试JavaScript变得简单起来。那么该如何来做呢?接下来将告诉你一些在asp.net中调试JavaScirpt的技巧。
在asp.net中一步一步调试JavaScript,在这篇文章里我使用了IIS和Visual Studio 2005。
配置
第一步:
在Visual Studio 2003/05中打开一个已经存在的asp.net应用程序,其中要有一些JavaScript。
第二步:
当我们在微软的Visual Studio中创建了一个web项目,那么一个名为web.config的文件默认也被创建在了该web项目中。
我们检查一下它的设置
<compilation debug=”false” strict=”false” explicit=”true” />
把debug的属性该为true,如下
<compilation debug=”true” strict=”false” explicit=”true” />
第三步:
打开Internet 信息服务(IIS)管理器
开始->管理工具->Internet 信息服务(IIS)管理器->网站->默认网站->右键单击你想调试的web项目->属性->虚拟目录选项卡->配置->在新打开的windows窗口中选择调试选项卡->把“启用 ASP 服务器端脚本调试”和“启用 ASP 客户端脚本调试”两个复选框选中。(请看截图1)
图1

第四步:
打开IE,单击菜单栏的“工具”->Internet 选项->高级选项卡->取消“禁用脚本调试(Internet Explorer)”和“禁用脚本调试(其他)”两个复选框的选中状态。(请看截图2)
图2

第五步:
现在,在Visual Studio IDE打开你的web项目。
注意:JavaScript一定要放到一个单独的.js文件里,而不能直接在.aspx页里写JavaScript。然后像对待.cs或.vb文件一样,在.js文件里给某行添加断点即可。
第六步:
运行你的web项目,程序将会在运行到你设置的断点处停止。
结论
[原文源码下载]
这是一个非常简单的调试JavaScript的例子。
文章来源:http://www.cnblogs.com/webabcd/archive/2007/01/06/613343.html
…………本文来自各大互联网博客RSS输出摘录,版权归原作者所有!
文章信息如下:
引用RSS源博文网址:http://www.cnblogs.com/alee/archive/2007/02/05/641283.html
~☆~庆生~☆~
[田中芳树]銀河英雄伝説 1-10
三国病人
然然的颜色理论
喜庆时刻
Visual Studio 2003 向 Visual Studio 2005 的移植
Visual Studio 2003 SP1,Visual Studio 2005 SP1和Orcas
Visual Studio2005 + Visual SourceSafe 2005 实现团队开发、源…
Visual Sidekick (Visual Studio Add-in)
Visual Studio 2005 SP1 and Visual Basic 2005 Command-line C…
Visual Studio Unit Testing Extends to Visual Studio Profess…
《Visual FoxPro 权威指南》新增内容——Visual FoxPro Sedna B…
re: Visual Studio 2005 SP1 Beta and Visual Studio support f…
Porting Visual C++ Code to Visual Studio 2005
[ZZ]HOW TO:使用 Visual Studio .NET 将 SQL Server 2000 存储�…
《Anime Studio Pro v5》(Anime Studio Pro v5)v5
《CodeGear RAD Studio 2007》(CodeGear RAD Studio 2007)11.0.2…
百度搜索:[导入][翻译]在Visual Studio中调试JavaScript
Google搜索:[导入][翻译]在Visual Studio中调试JavaScript
雅虎搜索:[导入][翻译]在Visual Studio中调试JavaScript
SOSO搜索:[导入][翻译]在Visual Studio中调试JavaScript
URL Encoding
(or: ‘What are those “%20″ codes in URLs?’)
= Index DOT Html by Brian Wilson =
| Main Index | Element Index | Element Tree | HTML Support History |
| RFC 1738 | Which characters must be encoded and why How to URL encode characters | URL encode a character |
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RFC 1738: Uniform Resource Locators (URL) specification The specification for URLs (RFC 1738, Dec. ’94) poses a problem, in that it limits the use of allowed characters in URLs to only a limited subset of the US-ASCII character set:
HTML, on the other hand, allows the entire range of the ISO-8859-1 (ISO-Latin) character set to be used in documents – and HTML4 expands the allowable range to include all of the Unicode character set as well. In the case of non-ISO-8859-1 characters (characters above FF hex/255 decimal in the Unicode set), they just can not be used in URLs, because there is no safe way to specify character set information in the URL content yet [RFC2396.] URLs should be encoded everywhere in an HTML document that a URL is referenced to import an object (A, APPLET, AREA, BASE, BGSOUND, BODY, EMBED, FORM, FRAME, IFRAME, ILAYER, IMG, ISINDEX, INPUT, LAYER, LINK, OBJECT, SCRIPT, SOUND, TABLE, TD, TH, and TR elements.) What characters need to be encoded and why?
How are characters URL encoded? URL encoding of a character consists of a “%” symbol, followed by the two-digit hexadecimal representation (case-insensitive) of the ISO-Latin code point for the character.
The box below allows you to convert content between its unencoded and encoded forms. The initial input state is considered to be “unencoded” (hit ‘Convert’ at the beginning to start in the encoded state.) Further, to allow actual URLs to be encoded, this little converter does not encode URL syntax characters (the “;”, “/”, “?”, “:”, “@”, “=”, “#” and “&” characters)…if you also need to encode these characters for any reason, see the “Reserved characters” table above for the appropriate encoded values. NOTE:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
http://topic.csdn.net/t/20020830/19/984137.html
http://www.cnblogs.com/ASPNET2008/archive/2008/09/16/1292028.html
你会在C#的类库中添加web service引用吗?
本文并不是什么高深的文章,只是VS2008应用中的一小部分,但小部分你不一定会,要不你试试:
本人对于分布式开发应用的并不多,这次正好有一个项目要应用web service,我的开发环境是vs2008,之所以没有选择现在热门的WCF,本人有如下原因:
1:负责开发Web Service的小组对于vs2008应用不多,更不能奢望令人垂涎的WCF。
2:项目开发时间有限,根本不许你去拿项目做实验。
3:项目改动不大,web service足够对付需求。
由于我们的项目是从1.1直接升级到3.5,升级的时候,把web项目直接改成类库,这样无论环境发生多大的变化,只要能够兼容低级版本,所有原来的文件都是能用的,例如,1.1中创建一个网页,会有一个resx的资源文件,但是3.5中创建的网页则是designer.cs,以后如果再升级,只要把相关新文件类型创建好,然后添加到类库中,这样同样可以享用高级版本的功能,说的比较含糊,不知道大家是否明白。
现在要做的就是在这样的类库中引用web service,凭直觉就认为,只要添加一下引用不就行了吗?其实是这样的吗?
先说下在web application项目中如何引用:
第一:对web项目点击右键,如图一。

第二:点击添加web 引用,如下图二.

第三:查找可用的web service,如果是本解决方案内的直接搜索就行,如果是外部的,就输入有效地址然后查询,这里面可以测试web service,如图三:

第四:添加引用成功后就会出现Web References目录.如图四,然后就是调用了。

这应该就是最基本的添加web service引用的方法了。当时我也是按照这样的思路去添加。发现类库中点击右键并没有出现我熟悉的 “添加web 引用”,而出现图五效果:

此时我认为可能是升级的原因变成”添加服务引用“,接下来的操作和上面操作相同.点击出现图六;

输入web service地址,测试连接都成功,添加成功后出现图七:

然而调用的时候并不像想像的那么成功,结果可想而之,我添加的WCF服务。这里错误代码我就不好意思帖了。知道引用对象错了后,就是让类库来添加web service,然而点击类库后,右键并没有”添加web 服务“的选项。如何让类库支持添加web service引用成了我最大的难题。此时我咨询了同事,基本排除程序问题,因为通过网站形式可以正常访问web service。
园子里的朋友你们在VS08中的类库会添加web servece引用吗?我baidu了一下午,毫无结果,最好只好再问别的同事,几经周折,有一个太有才的同事说类库可以添加web service引用,我不敢相信,我把方法贴出来show一下;
第一:对目标类库点击右键,如图六:
第二:点击“添加服务引用”如图八:

关键是图左下角的“高级”按键。点击如图九:

第三:注意了,同样是左下角,“添加WEB引用”的按钮终于出现了,点击如图十:之后的操作就不多说了。

到这里,就最终完成了类库中添加web service引用的目的。操作并不复杂,只是实在是想不明白,VS为什么要把添加web引用放的这么隐蔽呢,难道是为了WCF的推广?
posted on 2008-09-16 21:05 姜敏 阅读(2464) 评论(17) 编辑 收藏 网摘 所属分类: ASP.NET2.0
![]()
Feedback
#1楼 2008-09-16 21:53 ocean
兄弟,我也不知道这个问题怎么解释,我经常用,从来没发现这个问题,至少我这里本身就有add web service菜单。
回复 引用 查看
#2楼 2008-09-16 22:03 ocean
还有一个问题就是,对于一个web service来说,你添加服务引用也是没有任何问题的,因为生成本地代理类就是通过web service或者WCF的元信息来生成的。我刚刚又验证了一下,我通过add service的形式添加了一个web servcie,仍然可以正常运行成功,也印证了我原先的理解。你的那个选项应该只是按照完全的.NET2.0的形式来生成代理类,而VS2008生成的代理类有一些改进。
换句话说,你可以将web service看成一种特殊的WCF。 回复 引用 查看
#3楼 [楼主] 2008-09-16 22:06 姜敏
@ ocean
对,如果你不是在类库中添加就不会遇到以上问题 回复 引用 查看
#4楼 2008-09-16 22:06 潮儿
@ocean
博主说的是类库,你那个好像是web应用程序吧。 回复 引用 查看
#5楼 2008-09-16 22:09 Steven Chen
@ocean
今天下午刚刚用添加WCF的方式添加了WebService,发现不能工作,最后按照lz的方法添加的。
你说能工作,看来是我有些地方没有做对,明天公司再看看代码。 回复 引用 查看
#6楼 [楼主] 2008-09-16 22:10 姜敏
对于web application来说,应该是没有问题的,这里 ocean有实验,本人并没有亲自实验过.对于class librairy来说,朋友们不妨亲自试下.有些东西不亲自遇到还真不知道其中的困难. 回复 引用 查看
#7楼 [楼主] 2008-09-16 22:12 姜敏
@Steven Chen
看来这个问题还不止我一人遇到过啊,希望给朋友带来些帮助. 回复 引用 查看
#8楼 2008-09-16 22:18 Steven Chen
俄 果然如lz所说,webApplication出现了 添加Web引用, 但是类库项目,Console程序,Winform程序都不可以的。
我想可能是MS的一个小Bug,或者说MS不想让我们在用以传统的WebService的client了,而是使用类似于WCFClient的咚咚。 回复 引用 查看
#9楼 2008-09-16 22:21 ocean
刚刚没仔细看,不好意思。
我又试验了一下类库,新建了一个类库,然后add service,然后再在web app里面引用这个类库,结果一样,完全能调用啊。
你在类库里面add service还是在web app里面add service,都是用同一个命令行工具来生成代理类的,我比较了一下他们生成的代理类,好像没什么区别。所以在类库里面添加web service直接add service就可以了。我认为那个额外的选项只是为了生成完全和.NET2.0一模一样的代理类的,而VS2008带的代理类生成工具,生成的代理类和VS2005生成的有些差异的。我们也可以用一个命令行自己生成代理类,那个命令我忘记了,你可以自己查查。
其实我觉得不难理解,微软没有道理为web app和class lib各写一个代理类生成工具,所以生成的代理类应该完全一样的。 回复 引用 查看
#10楼 2008-09-16 22:25 Steven Chen
@ocean
应该是我某个操作没有弄好,明天再去公司看看,多谢 回复 引用 查看
#11楼 2008-09-16 22:25 ocean
我认为你可以先写一个简单的web service试验一下,看看是否正常。另外我认为可以单步调式一下,看看不能正常工作的原因。因为你add service的方式去添加一个web service,能成功生成代理类,所以VS2008是完全支持这种方式的,如果都不能工作,那就根本不会给你生成代理类了。
当然我的web service也是在VS2008里面写的,不知道有没有关系,不过web service是个标准,用VS2005和VS2008写好像没什么关系吧。 回复 引用 查看
#12楼 2008-09-16 22:36 Steven Chen
作了个测试,WebService可以通过VS生成的WCFClient调用,多谢ocean 回复 引用 查看
#13楼 [楼主] 2008-09-16 23:14 姜敏
@ocean
有是否有试过这们的情况,你的web网站,并不是web app而就是一个类库呢?你这样试下,类库也是可以当网站运行的,只是不能添加web app里面的文件,只能手工加.在没有web app的情况下试下,看添加web服务是否成功.我认为是不可靠的. 回复 引用 查看
#14楼 2008-09-17 10:41 赵俊
标题党,你文章的名字应该改为
“你会在C#的类库中添加web service引用吗? ” 回复 引用 查看
#15楼 [楼主] 2008-09-17 12:13 姜敏
#16楼 2008-09-17 17:11 狼Robot

楼主的VS2008什么版本的,我的就有添加web引用.
回复 引用 查看
#17楼 2008-09-17 17:43 飘遥
1.项目属性改成.net2.0即可
2.VS生成了一陀无关的代码和文件,WSDL很好用,如果不想硬编码URL,在配置文件中手工配置。 回复 引用 查看
| 标题 | 请输入标题 | |||||||
| 姓名 | 请输入你的姓名 | |||||||
| 主页 | ||||||||
| (博主才能看到)邮件地址无效 | ||||||||
|
||||||||
| Remember Me? | ||||||||
| 登录 使用高级评论 新用户注册 返回页首 恢复上次提交 | ||||||||
| [使用Ctrl+Enter键可以直接提交] | ||||||||
| 该文被作者在 2008-09-17 12:12 编辑过 | ||||||||
| Google站内搜索 | ||||||||
![]() ![]() |
||||||||
![]() China-pub 计算机图书网上专卖店!6.5万品种 2-8折! 近千种 9-95 新二手计算图书火热销售中! 开发者征途系统新作:《设计模式——基于C#的工程化实现及扩展》 |
||||||||
http://blog.csdn.net/vipxiaotian/archive/2008/03/13/2176090.aspx
此项目的URL位于Internet区域收藏
新一篇: Installing UrlRewriter.NET on Windows Server 2003 | 旧一篇: SQL Server 中易混淆的数据类型以及数据类型详解
无法在Web服务器上启动调试。您不具备调试此应用程序的权限,此项目的URL位于Internet区域一般用下面的方法可以解决:
1:确认在“配置属性”中的“启用ASP.NET调试”为”True”
2:确认你的”web.config”中的”debug=true”
3:若你安装过Win2000 SP4后,则要在命令行执行”regsvr32 i aspnet_isap.dll”
4:在IIS里查看站点信息,选中”目录安全性”,里面有”匿名访问和身份验证控制”,再点击”编辑..”,确认”集成Windows身份验证”选项被选中
5:在IE选项->”安全设置”->”自定义级别”里有”用户验证”,确认选中”自动使用当前用户名和密码登录”
6:运行C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_regiis.exe -i
7:控制面板–管理工具–计算机管理–本地用户和组–用户,双击ASPNET用户,为其隶属于添加Administrators用户
我的问题用第5种方法解决了
“/FormServer”应用程序中的服务器错误。
——————————————————————————–
运行时错误
说明: 服务器上出现应用程序错误。此应用程序的当前自定义错误设置禁止远程查看应用程序错误的详细信息(出于安全原因)。但可以通过在本地服务器计算机上运行的浏览器查看。
详细信息: 若要使他人能够在远程计算机上查看此特定错误信息的详细信息,请在位于当前 Web 应用程序根目录下的“web.config”配置文件中创建一个 <customErrors> 标记。然后应将此 <customErrors> 标记的“mode”属性设置为“Off”。
<!– Web.Config 配置文件 –>
<configuration>
<system.web>
<customErrors mode=”Off”/>
</system.web>
</configuration>
注释: 通过修改应用程序的 <customErrors> 配置标记的“defaultRedirect”属性,使之指向自定义错误页的 URL,可以用自定义错误页替换所看到的当前错误页。
<!– Web.Config 配置文件 –>
<configuration>
<system.web>
<customErrors mode=”RemoteOnly” defaultRedirect=”mycustompage.htm”/>
</system.web>
</configuration>
|
这部分描述了在托管代码中调用COM组件的方法,
现存的COM组件在托管代码中作为中间件或者独立的功能实体是很有价值的资源
向.NET Framework报露COM组件
1. Import a type library as an assembly
COM的类型定义通常在type library(tlb)中,与之对应的是CLS兼容的编译器在assembly中产生类型元数据(type metadata),这两种类型信息的来源有很大的不同
Generating Metadata
COM的类型库(type libraries)可以是一个单独得Tlb文件,例如Loanlib.tlb。一些类型库被嵌入到DLL或者Exe的资源文件中,还有一些类型库在OLB或者OCX中。
找到包含你的COM类型的类型库之后,可以有以下的选择来生成包含元数据的交互程序集(interop assembly)
当添加一个给定的type library 的引用(reference)时,Visual Studio .NET生成包含元数据的交互程序集(interop assembly)。如果一个主交互程序集(primary interop assembly)有效的话,Visual Studio使用存在的assembly
To add a reference to a type library
1. Install the COM DLL or EXE file on your computer, unless a Windows Setup.exe performs the installation for you.
2. From the Project menu, select References.
3. Select the COM tab.
4. Select the type library from the Available References list, or browse for the TLB file.
5. Click OK.
![]() Type Library Importer
The Type Library Importer (Tlbimp.exe)是一个命令行的工具,它可以把COM的类型库中的Coclassed和interfaces转换成元数据。
使用方法:
tlbimp Loanlib.dll
TypeLibConverter Class
TypeLibConverter class(in the System.Runtime.InteropServices namespace)提供了转换类型库中的coclasses和interfaces到元数据的方法。
Custom Wrappers
当一个类型库不可用或者不正确时,一个可选的方法就是在托管代码重创建一个class或者interface定义的副本。然后编译代码在runtime在程序集中产生元数据
手动定义COM类型,必须遵循如下
• 准确的描述定义的coclasses和interfaces
• 一个编译器,例如C#编译器可以生成对应的.NET Framework的类的定义
• type library-to-assembly转化规则的相关知识
写一个自定义的包装类(wrapper)是一项应该很少使用的高级的技术。
2. Using COM Types in Managed Code
COM 在程序集中类型定义类似其他的托管代码。托管的客户可以使用通常的方式创建COM类型的新的实例并且像其他托管类一样通过元数据得到类的信息。方法的语法可以通过object viewer或者反射(reflection)得到。当COM的方法返回一个失败的结果时,.NET的客户得到一个对应的异常。
得到或者释放一个运行的COM对象的引用像得到或者释放其他的运行的托管对象的引用一样。当一个.NET的客户端得到或者释放COM对象的引用,运行时像其他的COM客户端那样维护COM对象的引用计数,.NET的客户端可以认为对象可以像其他的托管对象那样被垃圾收集。
下面是C#的客户端代码
[C#]
using System;
using LoanLib;
public class LoanApp {
public static void Main(String[] Args) {
Loan ln = new Loan();
if (Args.Length < 4)
{
Console.WriteLine(“Usage: ConLoan Balance Rate Term Payment”);
Console.WriteLine(“ Either Balance, Rate, Term, or Payment
must be 0″);
return;
}
ln.OpeningBalance = Convert.ToDouble(Args[0]);
ln.Rate = Convert.ToDouble(Args[1])/100.0;
ln.Term = Convert.ToInt16(Args[2]);
ln.Payment = Convert.ToDouble(Args[3]);
if (ln.OpeningBalance == 0.00) ln.ComputeOpeningBalance();
if (ln.Rate == 0.00) ln.ComputeRate();
if (ln.Term == 0) ln.ComputeTerm();
if (ln.Payment == 0.00) ln.ComputePayment();
Console.WriteLine(“Balance = {0,10:0.00}”, ln.OpeningBalance);
Console.WriteLine(“Rate = {0,10:0.0%}”, ln.Rate);
Console.WriteLine(“Term = {0,10:0.00}”, ln.Term);
Console.WriteLine(“Payment = {0,10:0.00}\n”, ln.Payment);
bool MorePmts;
double Balance = 0.0;
double Principal = 0.0;
double Interest = 0.0;
Console.WriteLine(“{0,4}{1,10}{2,12}{3,10}{4,12}”, “Nbr”, “Payment”,
“Principal”, “Interest”, “Balance”);
Console.WriteLine(“{0,4}{1,10}{2,12}{3,10}{4,12}”, “—”, “——-”,
“———”, “——–”, “——-”);
MorePmts = ln.GetFirstPmtDistribution(ln.Payment, out Balance,
out Principal, out Interest);
for (short PmtNbr = 1; MorePmts; PmtNbr++) {
Console.WriteLine(“{0,4}{1,10:0.00}{2,12:0.00}{3,10:0.00}
{4,12:0.00}”, PmtNbr, ln.Payment, Principal, Interest,
Balance);
MorePmts = ln.GetNextPmtDistribution(ln.Payment, ref Balance,
out Principal, out Interest);
}
}
}
|
首先将Com类型信息转换为.NET元数据
tlbimp sample.dll /out: sample_clw.dll
工具:Tlbimp.exe(类型库导入程序 )
参考:.Net framework SDK 文档
ms-help://MS.NETFrameworkSDK.CHS/cptools/html/cpgrftypelibraryimportertlbimpexe.htm
查看元数据
工具:ILDasm
参考:ms-help://MS.NETFrameworkSDK.CHS/cptutorials/html/il_dasm_tutorial.htm
测试程序:
生成一个console programme
在project->add reference里,选择com,browser你的com,select
ok,现在在你的bin目录下应该有一个sample_clw.dll了
下面我们来用sample_clw的方法
using System;
using sample_clw;
namespace CompConsole
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// Date:2003/6/20
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此处添加代码以启动应用程序
Console.Write (“=======Demo for Call com functions in c#=============\n”);
sampleClass sam = new sampleClass();
//now we call functions
//
sam.SayHello();
}
}
}
HOW TO: 使用 Visual C# .NET 与返回一致数组的 COM 服务器互操作
| 文章编号 | : | 305990 |
| 最后修改 | : | 2007年5月13日 |
| 修订 | : | 1.4 |
概要
下面的文件可以从 Microsoft 下载中心下载:
有关如何下载 Microsoft 支持文件的其他信息,请单击下面的文章编号,查看 Microsoft 知识库中的文章:
在发表当日 Microsoft 使用了最新的病毒检测软件以扫描此文件是否有病毒。一旦发表后,此文件就保存在安全服务器上,以防对它进行未经授权的更改。
COM 服务器示例
本示例中使用的服务器实现下面的接口:
interface IComArrsObj : IDispatch
{
HRESULT GetArrOfLongs([in] LONG nStartIdx, [in,out] LONG* pnCnt,
[out,size_is(,*pnCnt)] LONG** ppArr);
HRESULT GetArrOfUDTs ([in] LONG nStartIdx, [in,out] LONG* pnCnt,
[out,size_is(,*pnCnt)] MyUDT** ppArr);
HRESULT MyNextLongs ([in] LONG nReq, [out, size_is(nReq)] LONG *rgelt,
[out] LONG* pnFetched);
HRESULT MyNextUDTs ([in] LONG nReq, [out, size_is(nReq)] MyUDT* rgelt,
[out] LONG* pnFetched);
};
如此接口规范所示,本文介绍如何与返回 Long 值和用户定义类型 (UDT) 的数组的 COM 服务器互操作。 该代码还演示了服务器分配的数组(即 GetArrOfXxx 方法)和客户端分配的数组(即 MyNextXxx 方法,它们模仿 IEnumXxx 接口中的 Next 方法)之间的区别。
GetArrOfXxx 方法使用下面三个参数:
| ? | nStartIdx 是一个 [in] 参数,它指定传递回客户端的第一个元素的索引。 |
| ? | pnCnt 是一个 [in, out] 参数,它等于客户端在进入时请求的项数,以及服务器在退出时提供的项数。 如果在进入时 pnCnt 为 0,那么服务器就返回所有可用的元素。 |
| ? | ppArr 是一个由服务器分配的 [out] 数组。 其大小等于 *pnCnt。 |
MyNextXxx 方法使用下面三个参数:
| ? | nReq 是一个 [in] 参数,它指定客户端请求的项数。 |
| ? | rgelt 是一个 [out] 参数,它由客户端分配并由服务器填充。 只有第一个 *pnFetched 元素是有效的。 |
| ? | pnFetched 是一个 [out] 参数,它指定由服务器填充的项数。 |
对 COM Interop 方法的讨论
通常,在与 COM 服务器互操作时,有下面几种选择:
| ? | 使用 interop 程序集与 COM 服务器通讯。 如果有主 interop 程序集,则应使用该程序集。 否则,可以使用 Tlbimp.exe 实用工具或 Visual Studio .NET 的导入功能生成 interop 程序集。 |
| ? | 直接在 Visual C# .Net 源代码中声明 COM 对象和接口。 如果需要与 COM 服务器公开的有限个对象和接口互操作,则可使用此选项。 |
| ? | 您可以增强上面任一选择,做法是:添加一个托管类,该类为与 COM 服务器互操作所需要的粗略导入类提供一个熟悉的托管接口。 |
如果有主 interop 程序集,则始终应使用该程序集,因为主 interop 程序集提供一个经过充分测试的,经证明可靠的 interop 层来访问 COM 服务器功能。 在主 interop 程序集可用时,仍可以选择第三种方式,并为主 interop 程序集导出的类型提供托管接口。
在主 interop 程序集不可用时,所选择的方式取决于应用程序是否需要公开从 COM 服务器导入的类型。 在应用程序包括多个程序集,而这些程序集交换从 COM 服务器导入类型的数据时,可能会发生这种情况。
如果托管应用程序包含多个程序集,而这些程序集使用 COM 服务器并且需要公开从 COM 服务器导入的数据类型,则应使用共享程序集来定义 interop 程序集。 在这种情况下,强烈建议使用主 interop 程序集。
如果不需要在各程序集间公开 COM 服务器中的数据类型,则可以为 interop 程序集使用私有程序集(第一种选择)或者在 Visual C# .Net 代码中定义 COM 类型(第二种选择)。
有关更多信息,请访问下面的 Microsoft Developer Network (MSDN) Web 站点:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpcondeployinginteropapplication.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpcondeployinginteropapplication.asp)
使用 Interop 程序集
如果有主 interop 程序集,则始终应使用该程序集。 使用主 interop 程序集提供了一个经过充分测试的,经证明可靠的 interop 层来访问 COM 服务器功能。 此外,Tlbimp.exe 提供了一种自动生成 interop 程序集的方法,自动生成代码具有一些优点,如速度快,正确率高。 此外,当托管客户端需要访问由 COM 服务器公开的大量方法和接口时,则不能使用第二种选择。 在这些情况下,可以使用 interop 程序集访问 COM 服务器所提供的功能。 可以使用下列方法之一获取 interop 程序集:
| ? | 从发布 COM 服务器(需要与其互操作)的软件供应商获取主 interop 程序集。 | ||||||
| ? | 从 Visual Studio .NET 生成主 interop 程序集。 若要调用 Tlbimp.exe 生成需要的 interop 程序集,请单击项目菜单上的添加引用,单击 COM 选项卡,然后选择 COM 服务器的条目。 | ||||||
| ? | 手动生成 interop 程序集。 为此,在 COM 服务器类型库(即 .tlb、.dll、.ocx 或 .exe 文件)上调用 Tlbimp.exe。 与依赖 Visual Studio .NET 的默认命令行参数相比,这使您对 Tlbimp.exe 有更好的控制。 | ||||||
| ? | 为了获得最大的灵活性,请按照下列步骤操作: | ||||||
| ? |
|
由于 COM 类型库格式的局限性,Tlbimp.exe 无法为本文示例中的 COM 服务器生成正确的接口定义。 因此,本文中的示例使用第四种选择。 若要使用此选择,请按照下列步骤操作:
| 1. | 在 COM 服务器动态链接库 (DLL) 上运行 Tlbimp.exe,如下所示: |
| 2. |
> tlbimp /out:Interop_1.ComArrs_1_0.DLL /namespace:ComArrs_1 ComArrs.DLL |
| 3. | 在生成的 DLL 上运行 Ildasm.exe,如下所示: |
| 4. |
> ildasm /out=Interop_1.ComArrs_1_0.IL Interop_1.ComArrs_1_0.DLL |
| 5. | 在生成的中间语言文件中修改方法签名,以便根据您的需要修改封送处理。 每个方法在中间语言中都出现两次,因此必须在接口的声明和类的声明中进行这些更改:
.method public hidebysig newslot virtual abstract
instance void GetArrOfLongs([in] int32 nStartIdx,
[in][out] int32& pnCnt,
// Replace this: [out] native int ppArr) runtime managed internalcall
// with this:
[out] native int& ppArr) runtime managed internalcall
.method public hidebysig newslot virtual abstract
instance void GetArrOfUDTs([in] int32 nStartIdx,
[in][out] int32& pnCnt,
// Replace this: [out] valuetype ComArrs_1.MyUDT& marshal( lpstruct) ppArr) runtime managed internalcall
// With this:
[out] native int& ppArr) runtime managed internalcall
.method public hidebysig newslot virtual abstract
instance void MyNextLongs([in] int32 nReq,
// Replace this: [out] int32& rgelt,
// With this:
[out] int32[] marshal([ + 1]) rgelt,
[out] int32& pnFetched) runtime managed internalcall
.method public hidebysig newslot virtual abstract
instance void MyNextUDTs([in] int32 nReq,
// Replace this: [out] valuetype ComArrs_1.MyUDT& rgelt,
// With this:
[out] valuetype ComArrs_1.MyUDT[] marshal([ + 1]) rgelt,
[out] int32& pnFetched) runtime managed internalcall
|
| 6. | 使用 Ilasm.exe 重新生成 interop 程序集,如下所示:
> ilasm /dll /resource=Interop_1.ComArrs_1_0.res Interop_1.ComArrs_1_0.il /out=Interop_1.ComArrs_1_0.dll |
| 7. | 在 Visual C# .Net 客户端项目中引用新的 interop 程序集。 为此,请单击项目菜单上的添加引用,然后浏览到最近生成的 DLL。 |
| 8. | 在托管客户端编写测试代码,如下所示:
public static void Run ( )
{
Console.WriteLine ( "\nTesting hand-edited IL of TlbImp-ed COM class:" );
Console.WriteLine ( "=============================================:" );
Console.WriteLine ( "Creating COM object" );
ComArrs_1.CComArrsObj o = new ComArrs_1.CComArrsObj ( );
Console.WriteLine ( "Calling GetArrOfLongs( )" );
int cnt = 0;
IntPtr rAddr;
o.GetArrOfLongs ( 0, ref cnt, out rAddr );
int [ ] r = new int [ cnt ];
// Marshal the array from an unmanaged to a managed heap.
Marshal.Copy ( rAddr, r, 0, cnt );
// Release the unmanaged array.
Marshal.FreeCoTaskMem ( rAddr );
Utils.PrintArray ( r, elementFormatter );
Console.WriteLine ( "Calling GetArrOfUDTs( )" );
cnt = 0;
IntPtr ruAddr;
o.GetArrOfUDTs ( 0, ref cnt, out ruAddr );
ComArrs_1.MyUDT [ ] ru = new ComArrs_1.MyUDT [ cnt ];
// Marshal the array, element by element, from an unmanaged to a managed heap.
for (int i = 0, elemOffs = (int) ruAddr; i < cnt; i++ )
{
ru[i] = ( ComArrs_1.MyUDT ) Marshal.PtrToStructure (
(IntPtr) elemOffs, typeof ( ComArrs_1.MyUDT ) );
elemOffs += Marshal.SizeOf ( typeof ( ComArrs_1.MyUDT ) );
}
// Release the unmanaged array.
Marshal.FreeCoTaskMem ( ruAddr );
Utils.PrintArray ( ru, elementFormatter );
Console.WriteLine ( "Calling MyNextLongs( )" );
int [ ] q = new int [ 20 ];
// The marshalling is performed automatically.
o.MyNextLongs ( q.GetLength(0), q, out cnt );
Utils.PrintArray ( q, elementFormatter );
Console.WriteLine ( "Calling MyNextUDTs( )" );
ru = new ComArrs_1.MyUDT[20];
// The marshalling is performed automatically.
o.MyNextUDTs ( ru.GetLength(0), ru, out cnt );
Utils.PrintArray ( ru, elementFormatter );
}
|
为 COM 服务器使用 C# 声明
与使用 interop 程序集相比,此选择的优势在于消除了提供另一个文件(即 interop 程序集)的必要。
若要指定必需的元素并调用方法,请按照下列步骤操作:
备注: 由“COM 服务器示例”部分提供的 COM 接口开始。
| 1. | 如下所示定义 UDT: |
| 2. |
// MyUDT as defined in the server.
[Guid("190A418D-B113-40d4-A22C-20EF9EAC3E33")]
[StructLayout(LayoutKind.Sequential)]
struct MyUDT
{
[MarshalAs(UnmanagedType.BStr)]
public string aBstr;
public int aLong;
public bool aBool;
}
|
| 3. | 如下所示定义 COM 接口: |
| 4. |
// A possible C# representation of the interface.
[InterfaceType(ComInterfaceType.InterfaceIsDual),
Guid("78D4F391-B10B-4B80-A2D1-1B4C583DCAEC")]
interface IComArrsObj
{
void GetArrOfLongs(int startIdx, ref int cnt, out IntPtr arrAddr);
void GetArrOfUDTs (int startIdx, ref int cnt, out IntPtr arrAddr);
void MyNextLongs (int req,
[MarshalAs(UnmanagedType.LPArray, Out]
int [ ] rgelt,
out int fetched);
void MyNextUDTs (int req,
[MarshalAs(UnmanagedType.LPArray, Out]
MyUDT [ ] rgelt,
out int fetched);
}
|
| 5. | 如下所示定义 COM 服务器:
// The coclass.
[ComImport, Guid("056A32CF-D716-4902-BCD2-ED7F070D9E36")]
class CComArrsObj
{
}
|
| 6. | 如下所示在 COM 服务器上调用方法:
public static void Run ( )
{
Console.WriteLine ( "\nTesting C#-declared COM class and interfaces:" );
Console.WriteLine ( "============================================:" );
Console.WriteLine ( "Creating COM object" );
ComArrs_0.IComArrsObj icao = new ComArrs_0.CComArrsObj ( )
as ComArrs_0.IComArrsObj;
Console.WriteLine ( "Calling GetArrOfLongs( )" );
int cnt = 0;
IntPtr rAddr;
icao.GetArrOfLongs ( 0, ref cnt, out rAddr );
int [ ] r = new int [ cnt ];
// Marshal the array from an unmanaged to a managed heap.
Marshal.Copy ( rAddr, r, 0, cnt );
// Release the unmanaged array.
Marshal.FreeCoTaskMem ( rAddr );
Utils.PrintArray ( r, elementFormatter );
Console.WriteLine ( "Calling GetArrOfUDTs( )" );
cnt = 0;
IntPtr ruAddr;
icao.GetArrOfUDTs ( 0, ref cnt, out ruAddr );
ComArrs_0.MyUDT [ ] ru = new ComArrs_0.MyUDT [ cnt ];
// Marshal the array, element by element, from an unmanaged to a managed heap.
for ( int i = 0, elemOffs = (int) ruAddr; i < cnt; i++ )
{
ru[i] = ( ComArrs_0.MyUDT ) Marshal.PtrToStructure (
(IntPtr) elemOffs, typeof ( ComArrs_0.MyUDT ) );
elemOffs += Marshal.SizeOf ( typeof ( ComArrs_0.MyUDT ) );
}
// Release the unmanaged array.
Marshal.FreeCoTaskMem ( ruAddr );
Utils.PrintArray ( ru, elementFormatter );
Console.WriteLine ( "Calling MyNextLongs( )" );
int [ ] q = new int [20];
// The marshalling is performed automatically.
icao.MyNextLongs ( q.GetLength(0), q, out cnt );
Utils.PrintArray ( q, elementFormatter );
Console.WriteLine ( "Calling MyNextUDTs( )" );
ru = new ComArrs_0.MyUDT [20];
// The marshalling is performed automatically.
icao.MyNextUDTs ( ru.GetLength(0), ru, out cnt );
Utils.PrintArray ( ru, elementFormatter );
}
|
添加托管包装类
托管包装类可以基于前面两个选择中的任何一个。 它可以使用第一个选择中 TlbImp 生成的包装,也可以封装对 COM 服务器的 Visual C# .Net 声明的引用。 若要使用后一个选择来生成托管包装类,请按照下列步骤操作:
| 1. | 添加一个对包含 COM 服务器的托管定义的 interop 程序集的引用。 |
| 2. | 依据 TlbImp 生成的类定义托管包装类,如下所示: |
| 3. |
class MgdComArrs
{
public MgdComArrs ( )
{ cao_ = new ComArrs_1.CComArrsObj( ); }
public int [ ] GetArrOfLongs ( int startIdx, ref int cnt )
{
IntPtr rAddr;
cao_.GetArrOfLongs ( startIdx, ref cnt, out rAddr );
int [ ] r = new int [ cnt ];
// Marshal the array from an unmanaged to a managed heap.
Marshal.Copy ( rAddr, r, 0, cnt );
// Release the unmanaged array.
Marshal.FreeCoTaskMem ( rAddr );
return r;
}
public ComArrs_1.MyUDT [ ] GetArrOfUDTs ( int startIdx, ref int cnt )
{
IntPtr ruAddr;
cao_.GetArrOfUDTs ( startIdx, ref cnt, out ruAddr );
ComArrs_1.MyUDT [ ] ru = new ComArrs_1.MyUDT [ cnt ];
// Marshal the array, element by element.
for ( int i = 0, elemOffs = (int) ruAddr; i < cnt; i++ )
{
ru[i] = (ComArrs_1.MyUDT) Marshal.PtrToStructure (
(IntPtr) elemOffs, typeof ( ComArrs_1.MyUDT ) );
elemOffs += Marshal.SizeOf ( typeof ( ComArrs_1.MyUDT ) )'
}
// Release the unmanaged array.
Marshal.FreeCoTaskMem ( ruAddr );
return ru;
}
public int MyNextLongs ( int req, int [ ] r )
{
int fetched;
cao_.MyNextLongs ( req, r, out fetched );
return fetched;
}
public int MyNextUDTs ( int req, ComArrs_1.MyUDT [ ] r )
{
int fetched;
cao_.MyNextUDTs ( req, r, out fetched );
return fetched;
}
private ComArrs_1.CComArrsObj cao_;
}
|
| 4. | 如下所示编写客户端代码: |
| 5. |
public static void Run ( )
{
Console.WriteLine ( "\nTesting managed wrapper definition of COM class:" );
Console.WriteLine ( "===============================================:" );
Console.WriteLine ( "Creating managed wrapper" );
MgdComArrs mcao = new MgdComArrs( );
Console.WriteLine ( "Calling GetArrOfLongs( )" );
int cnt = 0;
int [ ] r = mcao.GetArrOfLongs ( 0, ref cnt );
Utils.PrintArray ( r, elementFormatter );
Console.WriteLine ( "Calling GetArrOfUDTs( )" );
cnt = 0;
ComArrs_1.MyUDT [ ] ru = mcao.GetArrOfUDTs ( 0, ref cnt );
Utils.PrintArray ( ru, elementFormatter );
Console.WriteLine ( "Calling MyNextLongs( )" );
int [ ] q = new int [ 15 ];
mcao.MyNextLongs ( q.GetLength ( 0 ), q );
Utils.PrintArray ( q, elementFormatter );
Console.WriteLine ( "Calling MyNextUDTs( )" );
ru = new ComArrs_1.MyUDT [ 5 ];
mcao.MyNextUDTs ( ru.GetLength ( 0 ), ru );
Utils.PrintArray ( ru, elementFormatter );
}
|
参考
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpcondatamarshaling.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpcondatamarshaling.asp)
Interop Marshaling for COM(COM 的 Interop 封送处理)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconinteropmarshalingforcom.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconinteropmarshalingforcom.asp)
Applying Interop Attributes(应用 Interop 属性)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconapplyinginteropattributes.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconapplyinginteropattributes.asp)
Deploying an Interop Application(部署 Interop 应用程序)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpcondeployinginteropapplication.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpcondeployinginteropapplication.asp)
Microsoft Intermediate Language (MSIL)(Microsoft 中间语言)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconmicrosoftintermediatelanguagemsil.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconmicrosoftintermediatelanguagemsil.asp)
Type Library Importer (Tlbimp.exe)(类型库导入程序)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptools/html/cpgrftypelibraryimportertlbimpexe.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptools/html/cpgrftypelibraryimportertlbimpexe.asp)
MSIL Disassembler (Ildasm.exe)(MSIL 反汇编程序)
http://msdn2.microsoft.com/en-us/library/f7dy01k1(vs.71).aspx (http://msdn2.microsoft.com/en-us/library/f7dy01k1(vs.71).aspx)
ILDasm Tutorial(ILDasm 教程)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptut/html/il_dasm_tutorial.asp (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cptut/html/il_dasm_tutorial.asp)
MSIL Assembler (Ilasm.exe)(MSIL 汇编程序)
http://msdn2.microsoft.com/en-us/library/496e4ekx(vs.71).aspx (http://msdn2.microsoft.com/en-us/library/496e4ekx(vs.71).aspx)
COM Interop Sample: .NET Client and COM Server(COM Interop 示例:.NET 客户端和 COM 服务器)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguidnf/html/cpconcominteropsamplenetclientcomserver.asp
11款完全免费的Web设计工具
ugmbbc发布于 2008-10-15 23:22:23| 次阅读 字体:大 小 打印预览
感谢Sonny的投递今天,我将列举11款完全免费并且很有用的基于Web的设计工具,其中的多数可能不是很有名,但是肯定会让你眼睛一亮!
下面就来看看他们吧,如果您了解多正在使用更多更好的Web设计工具,欢迎分享。
1. splashup
splashup 是一款功能强大的图片编辑和管理软件,它基本上具有了所有菜鸟级和专业级设计者所需要的功能,它的界面非常的人性化,并且提供对多图像编辑的支持,虽然功能不如Photoshop(十大Photoshop教程网站)(25张顶级的Photoshop图片)(22张梦幻派photoshop教程级图片)强大,但也提供了对图层,滤镜,选取的支持.如果您要使用splashuo,需要拥有他的账户。
2. ResizR

ResizR 是一款漂亮,免费并且很有用的小助手,它允许你自由的缩放本地硬盘或Web上的图片,不过目前只支持JPG格式的图片,由于ResizR 每隔 60 分钟会清除之前操作过的图片, 所以不用担心版权问题! 此外ResizR 的网站上还提供了用于Firefox的扩展(13个网页设计必备的Firefox扩展).
3. Adhesiontext

Adhesiontext, 一款动态的文本工具, 它能够提供不同语言字符集中同一字符串的不同样式, 对于处于开发早期的字体设计和开发者来说尤其有用. 目前支持的语种有: 英语,法语,德语,西班牙语,葡萄牙语,加泰罗尼亚语,荷兰语。
4. CSS Layout Generator

CSS Layout Generator(7个最好的网上CSS资源) 能够在您设定参数的情况下, 生成浮动宽度或者固定宽度的流体布局, 可以有三栏式的, 也可以具有头部[Header]或脚部[Footer], 各栏的宽度可以以多种尺度来衡量, 例如像素[px],等宽字体宽度[em],百分比[%]等. 此外,你你还可以选择生成文档的Doctype属性, 是HTML或者XHTML。
5. PatternCooler

把您自己的色彩添加到复古和当代的设计样式中, 或者浏览数以千计的预定义样式, 该网站上的所有艺术作品可以在个人博客, 手机壁纸, MySpace的简介和非商业网站项目中免费使用!
6. ThinkFree

ThinkFree, 在线的Office, 允许您快速的查看, 组织, 编辑, 管理以及共享文档, 于Google的Docs服务颇有几分相似。
7. ColorSchemer ColorPix

一款小巧的Color Picker, 能够随着您鼠标的移动迅速的抓取屏幕颜色, 并将其转换为不同格式的颜色标识, 其优点是无需安装, 只需下载直接运行, 使用时, 可以设定不同的缩放程度, 一边夺取等精确的颜色信息, 合适的时候, 单击就可以把当前颜色代码复制到剪贴板, 此外, 改程序可以被设定为”置顶”,位于所有程序的窗口的上方。
8. Net2ftp

基于Web的FTP客户端, 仅仅利用浏览器就可以轻松的管理您的网站, 特别适用于那些服务器托管的网站管理员, 使用它可以随意的编辑代码, 上传/下载文件, 拷贝/移动/删除/重命名 文件或者目录。
9. Remember The Milk

直观的界面来帮助您管理日常事物, 就如Google Calendar, 可以随意安排日程, 众多的快捷键, 收到Gmail,SMS,或其他即时信息的提醒, 如发送邮件般简单的任务添加, 会给您空前的体验。
10. Picreflect

Picreflect 快速的生成图片倒影, 缩放图片, 或者任意角度的旋转。

专心于颜色的设计理论, 尤其是Web设计和应用, 让Web Designers(16个优秀网站教你网站设计) 更好的理解颜色的重要性, 更容易的学习颜色理论, 此外还提供了几款常用的颜色工具, 如 Color Wizard, Color Wheel, 和Contrast Analyzer.
连接错误
2009/03/29http://hi.baidu.com/fionaguo/blog/item/d71e0010e06945f8c2ce7971.html
|
第一: 第二: http://blog.csdn.net/holym/archive/2006/11/14/1382909.aspx 解决外部符号错误:_main,_WinMain@16,__beginthreadex 1. Windows子系统设置错误, 提示: Windows项目要使用Windows子系统, 而不是Console, 可以这样设置: [Project] –> [Settings] –> 选择”Link”属性页, 2. Console子系统设置错误, 提示: 控制台项目要使用Console子系统, 而不是Windows, 设置: [Project] –> [Settings] –> 选择”Link”属性页, 3. 程序入口设置错误, 提示: 通常, MFC项目的程序入口函数是WinMain, 如果编译项目的Unicode版本, 程序入口必须改为wWinMainCRTStartup, 所以需要重新设置程序入口: [Project] –> [Settings] –> 选择”Link”属性页, 4. 线程运行时库设置错误, 提示: 这是因为MFC要使用多线程时库, 需要更改设置: [Project] –> [Settings] –> 选择”C/C++”属性页, 单线程: 不需要多线程调用时, 多用在DOS环境下 初学者在学习VC++的过程中,遇到的LNK2001错误的错误消息主要为: unresolved external symbol “symbol”(不确定的外部“符号”)。 如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或标签,将产生此错误消息。一般来说,发生错误的原因有两个:一是所引用的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本的连接库。 以下是可能产生LNK2001错误的原因: 一.由于编码错误导致的LNK2001 1.不相匹配的程序代码或模块定义(.DEF)文件能导致LNK2001。例如, 如果在C++源文件内声明了一变量“var1”,却试图在另一文件内以变量“VAR1”访问该变量,将发生该错误。 2.如果使用的内联函数是在.CPP文件内定义的,而不是在头文件内定义将导致LNK2001错误。 3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生LNK2001。 4.试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001。 5.要注意函数和变量的可公用性,只有全局变量、函数是可公用的。静态函数和静态变量具有相同的使用范围限制。当试图从文件外部访问任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。 函数内声明的变量(局部变量) 只能在该函数的范围内使用。 C++ 的全局常量只有静态连接性能。这不同于C,如果试图在C++的多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种方法是使用时给该变量赋以常数。 二.由于编译和链接的设置而造成的LNK2001 1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行库和MFC库在连接时由编译器写入目标文件模块, 但除非在文件中明确包含这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导致错误LNK2001。 2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC时将得到“unresolved external on _WinMain@16”的LNK2001错误信息。 3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内,源文件中对“func”的引用,在目标文件里即对“__imp__func” 的引用。如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在__imp__func上发生LNK2001;如果不使用/MD选项编 4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。 5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的问题。 6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可能包含早先的版本没有的符号和说明。 编程时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头文件里却关闭了函数内联(没有inline关键字),这时将得到该错误信息。为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。 8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。 |
http://blog.csdn.net/soloist/archive/2005/09/30/493238.aspx
为什么会出现LNK2005″符号已定义”的链接错误?收藏
新一篇: 在VC6.0中如何让new操作失败后抛出异常? | 旧一篇: Lua的语法是无歧义的吗?
许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误,而且通常是在使用第三方库时遇到的。对于这个问题,有的朋友可能不知其然,而有的朋友可能知其然却不知其所以然,那么本文就试图为大家彻底解开关于它的种种疑惑。大家都知道,从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码,然后由汇编器(assembler)翻译成机器指令(再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中;(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。
编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器,而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢?编译器认为函数与初始化了的全局变量都是强符号,而未初始化的全局变量则成了弱符号。比如有这么个源文件:
extern int errorno;
int buf[2] = {1,2};
int *p;
int main()
{
return 0;
}
其中main、buf是强符号,p是弱符号,而errorno则非强非弱,因为它只是个外部变量的使用声明。
有了强弱符号的概念,链接器(Unix平台)就会按如下规则(参考[1],p549~p550)处理与选择被多次定义的全局符号:
规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);
规则2: 如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么选择强符号;
规则3: 如果一个符号在所有目标文件中都是弱符号,那么选择其中任意一个;
虽然上述3条针对的是Unix平台的链接器,但据作者试验,至少VC6.0的linker也遵守这些规则。由此可知多个目标文件不能重复定义同名的函数与初始化了的全局变量,否则必然导致LNK2005和LNK1169两种链接错误。可是,有的时候我们并没有在自己的程序中发现这样的重定义现象,却也遇到了此种链接错误,这又是何解?嗯,问题稍微有点儿复杂,容我慢慢道来。
众所周知,ANSI C/C++ 定义了相当多的标准函数,而它们又分布在许多不同的目标文件中,如果直接以目标文件的形式提供给程序员使用的话,就需要他们确切地知道哪个函数存在于哪个目标文件中,并且在链接时显式地指定目标文件名才能成功地生成可执行文件,显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制,这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名,链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块,并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗?)。
程序库为开发者带来了方便,但同时也是某些混乱的根源。我们来看看链接器(Unix平台)是如何解析(resolve)对程序库的引用的(参考[1],p556)。
在符号解析(symbol resolution)阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合;(2)集合D是所有之前已被加入E的目标文件定义的符号集合;(3)集合U是未解析符号(unresolved symbols,即那些被E中目标文件引用过但在D中还不存在的符号)的集合。一开始,E、D、U都是空的。
(1): 对命令行中的每一个输入文件f,链接器确定它是目标文件还是库文件,如果它是目标文件,就把f加入到E,并把f中未解析的符号和已定义的符号分别加入到U、D集合中,然后处理下一个输入文件。
(2): 如果f是一个库文件,链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号,那么就把m加入到E中,并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point),此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。
(3): 当扫描完所有输入文件时如果U非空或者有同名的符号被多次加入D,链接器报告错误信息并退出。否则,它把E中的所有目标文件合并在一起生成可执行文件。
上述规则针对的是Unix平台链接器,而VC(至少VC6.0)linker则有相当的不同: 它首先依次处理命令行中出现的所有目标文件,然后依照顺序不停地扫描所有的库文件,直至U为空或者某遍(从头到尾依次把所有的库文件扫描完称为一遍)扫描过程中U、D无任何变化时结束扫描,此刻再根据U是否为空以及是否有同名符号重复加入D来决定是出错退出还是生成可执行文件。很明显Unix链接器对输入文件在命令行中出现的顺序十分敏感,而VC的算法则可最大限度地减少文件顺序对链接的影响。作者不清楚Unix下新的开发工具是否已经改进了相应的做法,欢迎有实践经验的朋友补充这方面的信息(补充于2005年10月10日: 经试验,使用gcc 3.2.3的MinGW 3.1.0的链接器表现与参考[1]描述的一致)。
VC带的编译器是cl.exe,它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib);/MT对应多线程静态版标准库(libcmt.lib),此时编译器会自动定义_MT宏;/MD对应多线程DLL版(导入库msvcrt.lib,DLL是msvcrt.dll),编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个_DEBUG宏,表示要使用对应标准库的调试版,因此/MLd对应调试版单线程静态标准库(libcd.lib),/MTd对应调试版多线程静态标准库(libcmtd.lib),/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib,DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库,可是当编译器干完了活,轮到链接器开工时它又如何得知一个个目标文件到底在思念谁?为了传递相思,我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和PE文件格式)存放一些指导链接器如何工作的信息,其中有一项就叫缺省库(default library),它指定了若干个库文件名,当链接器扫描该目标文件时将按照它们在目标模块中出现的顺序处理这些库名: 如果该库在当前输入文件列表中还不存在,那么便把它加入到输入文件列表末尾,否则略过。说到这里,我们先来做个小实验。写个顶顶简单的程序,然后保存为main.c :
/* main.c */
int main() { return 0; }
用下面这个命令编译main.c(什么?你从不用命令行来编译程序?这个……) :
cl /c main.c
/c是告诉cl只编译源文件,不用链接。因为/ML是缺省选项,所以上述命令也相当于: cl /c /ML main.c 。如果没什么问题的话(要出了问题才是活见鬼!当然除非你的环境变量没有设置好,这时你应该去VC的bin目录下找到vcvars32.bat文件然后运行它。),当前目录下会出现一个main.obj文件,这就是我们可爱的目标文件。随便用一个文本编辑器打开它(是的,文本编辑器,大胆地去做别害怕),搜索”defaultlib”字符串,通常你就会看到这样的东西: “-defaultlib:LIBC -defaultlib:OLDNAMES”。啊哈,没错,这就
是保存在目标文件中的缺省库信息。我们的目标文件显然指定了两个缺省库,一个是单线程静态版标准库libc.lib(这与/ML选项相符);一个是oldnames.lib(它是为了兼容微软以前的C/C++开发系统,基本不用了,为了简化讨论可以忽略它)。另外,如果在源程序中用了
/* xxxx代表实际的库文件名 */
#pragma comment(lib,”xxxx”)
编译指示命令(compiler directive)指定要链接的库,那么这个信息也会被保存到目标文件的缺省库信息项中,且位于缺省标准库之前。如果有多个这样的命令,那么对应库名在目标文件中出现的顺序与它们在源程序中出现的顺序完全一致(且都在缺省标准库之前)。
VC的链接器是link.exe,因为main.obj保存了缺省库信息,所以可以用
link main.obj libc.lib
或者
link main.obj
来生成可执行文件main.exe,这两个命令是等价的。但是如果你用
link main.obj libcd.lib
的话,链接器会给出一个警告: “warning LNK4098: defaultlib “LIBC” conflicts with use of other libs; use /NODEFAULTLIB:library”,因为你显式指定的标准库版本与目标文件的缺省值不一致。通常来说,应该保证链接器合并的所有目标文件指定的缺省标准库版本一致,否则编译器一定会给出上面的警告,而LNK2005和LNK1169链接错误则有时会出现有时不会。那么这个有时到底是什么时候?呵呵,别着急,下面的一切正是为喜欢追根究底的你准备的。
建一个源文件,就叫mylib.c,内容如下:
/* mylib.c */
#include <stdio.h>
void foo(void)
{
printf(“%s”,”I am from mylib!\n”);
}
用
cl /c /MLd mylib.c
命令编译,注意/MLd选项是指定libcd.lib为默认标准库。lib.exe是VC自带的用于将目标文件打包成程序库的命令,所以我们可以用
lib /OUT:my.lib mylib.obj
将mylib.obj打包成库,输出的库文件名是my.lib。接下来把main.c改成:
/* main.c */
void foo(void);
int main()
{
foo();
return 0;
}
用
cl /c main.c
编译,然后用
link main.obj my.lib
进行链接。这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169链接错误,你仅仅是得到了一条警告信息:”warning LNK4098: defaultlib “LIBCD” conflicts with use of other libs; use /NODEFAULTLIB:library”。我们根据前文所述的扫描规则来分析一下链接器此时做了些啥(加一个/VERBOSE选项就可以看到详尽的链接过程,但要注意,几乎所有的C编译器都会在符号前加一个下划线后再输出,所以在目标文件和链接输出信息中看到的符号名都比在源程序中见到的多出一个’_',此点不可不察。)。
一开始E、U、D都是空集。链接器首先扫描main.obj,把它的默认标准库libc.lib加入到输入文件列表末尾,它自己加入E集合,同时未解析的foo加入U,main加入D。接着扫描my.lib,因为这是个库,所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配,看是否有模块定义了U中的符号。结果mylib.obj确实定义了foo,于是它加入到E,foo从U转移到D,未解析的printf加入到U,指定的默认标准库libcd.lib也加到输入文件列表末尾(在libc.lib之后)。不断地在my.lib库的各模块上进行迭代以匹配U中的符号,直到U、D都不再变化。很明显,现在就已经到达了这么一个不动点,所以接着扫描下一个输入文件,就是libc.lib。链接器发现libc.lib里的printf.obj里定义有printf,于是printf从U移到D,printf.obj加入到E,它定义的所有符号加入到D,它里头的未解析符号加入到U。如果链接时没有指定/ENTRY(程序入口点选项),那么链接器默认的入口点就是函数mainCRTStartup(GUI程序的默认入口点则是WinMainCRTStartup),它在crt0.obj中被定义,所以crt0.obj及它直接或间接引用的模块(比如malloc.obj、free.obj等)都被加入到E中,这些目标模块指定的默认库(只crt0init.obj指定了kernel32.lib)加到输入文件列表末尾,同时更新U和D。不断匹配libc.lib中各模块直至到达不动点,然后处理libcd.lib,但是它里面的所有目标模块都没有定义U中的任何一个符号,所以链接器略过它进入到最后一个输入文件kernel32.lib。事实上,U中已有和将要加入的未解析符号都可以在其中找到定义,那么当处理完kernel32.lib时,U必然为空,于是链接器合并E中的所有模块生成可执行文件。
上文描述了虽然各目标模块指定了不同版本的缺省标准库但仍然链接成功的例子,接下来你将目睹因为这种不严谨而导致的悲惨失败。
修改mylib.c成这个样子:
#include <crtdbg.h>
void foo(void)
{
// just a test , don’t care memory leak
_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );
}
其中_malloc_dbg不是ANSI C的标准库函数,它是VC标准库提供的malloc的调试版,与相关函数配套能帮助开发者抓各种内存错误。使用它一定要定义_DEBUG宏,否则预处理器会把它自动转为malloc。继续用
cl /c /MLd mylib.c
lib /OUT:my.lib mylib.obj
编译打包。当再次用
link main.obj my.lib
进行链接时,我们看到了什么?天哪,一堆的LNK2005加上个贵为”fatal error”的LNK1169垫底,当然还少不了那个LNK4098。链接器是不是疯了?不,你冤枉可怜的链接器了,我拍胸脯保证它可是一直在尽心尽责地照章办事。
一开始E、U、D为空,链接器扫描main.obj,把libc.lib加到输入文件列表末尾,把main.obj加进E,把foo加进U,把main加进D。接着扫描my.lib,于是mylib.obj加入E,libcd.lib加到输入文件列表末尾,foo从U转移到D,_malloc_dbg加进U。然后扫描libc.lib,这时会发现libc.lib里任何一个目标模块都没有定义_malloc_dbg(它只在调试版的标准库中存在),所以不会有任何一个模块因为_malloc_dbg而加入E。但因为libc.lib中的crt0.obj定义了默认入口点函数mainCRTStartup,所以crt0.obj及它直接或间接引用的模块(比如malloc.obj、free.obj等)都被加入到E中,这些目标模块指定的默认库(只crt0init.obj指定了kernel32.lib)加到输入文件列表末尾,同时更新U和D。不断匹配libc.lib中各模块直至到达不动点后再处理libcd.lib,发现dbgheap.obj定义了_malloc_dbg,于是dbgheap.obj加入到E,它的未解析符号加入U,它定义的所有其它符号加入D,这时灾难便来了。之前malloc等符号已经在D中(随着libc.lib里的malloc.obj加入E而加入的),而dbgheap.obj及因它而引入的其它模块又定义了包括malloc在内的许多同名符号,导致了重定义冲突。所以链接器在处理完所有输入文件(是的,即使中途有重定义冲突它也会处理所有的文件以便生成一个完整的冲突列表)后只好报告: 这活儿没法儿干。
现在我们该知道,链接器完全没有责任,责任在我们自己的身上。是我们粗心地把缺省标准库版本不一致的目标文件(main.obj)与程序库(my.lib)链接起来,引发了大灾难。解决办法很简单,要么用/MLd选项来重编译main.c;要么用/ML选项重编译mylib.c;再或者干脆在链接时用/NODEFAULTLIB:XXX选项忽略默认库XXX,但这种方法非常不保险(想想为什么?),所以不推荐。
在上述例子中,我们拥有库my.lib的源代码(mylib.c),所以可以用不同的选项重新编译这些源代码并再次打包。可如果使用的是第三方的库,它并没有提供源代码,那么我们就只有改变自己程序的编译选项来适应这些库了。但是如何知道库中目标模块指定的默认库呢?其实VC提供的一个小工具便可以完成任务,这就是dumpbin.exe。运行下面这个命令
dumpbin /DIRECTIVES my.lib
然后在输出中找那些”Linker Directives”引导的信息,你一定会发现每一处这样的信息都会包含若干个类似”-defaultlib:XXXX”这样的字符串,其中XXXX便代表目标模块指定的缺省库名(注意,如果在编译时指定了/Zl选项,那么目标模块中将不会有defaultlib信息)。
知道了第三方库指定的默认标准库,再用合适的选项编译我们的应用程序,就可以避免LNK2005和LNK1169链接错误。喜欢IDE的朋友,你一样可以到 “Project属性” -> “C/C++” -> “代码生成(code generation)” -> “运行时库(run-time library)” 项下设置应用程序的默认标准库版本,这与命令行选项的效果是一样的。
我的解释:
首先阐述问题:在加入MFC之后最初的编译错误为:
nafxcwd.lib(appcore.obj) : error LNK2001: 无法解析的外部符号 ___argv nafxcwd.lib(appcore.obj) : error LNK2001: 无法解析的外部符号 ___argc nafxcwd.lib(apphelp.obj) : error LNK2019: 无法解析的外部符号 __mbctype ,该符号在函数 “void __stdcall AfxFormatStrings(class ATL::CStringT<char,class StrTraitMFC<char,class ATL::ChTraitsCRT<char> > > &,unsigned int,char const * const *,int)” (?AfxFormatStrings@@YGXAAV?$CStringT@DV?$StrTraitMFC@DV?$ChTraitsCRT@D@ATL@@@@@ATL@@IPBQBDH@Z) 中被引用…….. 这是因为工程当中设置了共享MFCdll引用,但是似乎并没有真正的加入MFC dll引用。先把其设置为静态的MFC引用。编译出现编译错误之后改为动态MFC共享。此时出现oeprator new和delete重定义的链接错误。
A LNK2005 error occurs when the CRT library and MFC libraries are linked in the wrong order in Visual C++
On This Page
SYMPTOMS
“void * __cdecl operator new(unsigned int)”(??2@YAPAXI@Z) already
defined in LIBCMTD.lib(new.obj)
“void __cdecl operator delete(void *)”(??3@YAXPAX@Z) already defined
in LIBCMTD.lib(dbgnew.obj)
“void * __cdecl operator new(unsigned int,int,char const *,int)”
(??2@YAPAXIHPBDH@Z) already defined in LIBCMTD.lib(dbgnew.obj)
MSVCRTD.LIB (dllmain.obj)
msvcrtd.lib(dllmain.obj)
CAUSE
RESOLUTION
Note The following steps are based on Visual C++ 6.0.
Solution One: Force Linker to Link Libraries in Correct Order
- On the Project menu, click Settings.
- In the Settings For view of the Project Settings dialog box, click to select the project configuration that is getting the link errors.
- On the Link tab, click to select Input in the Category combo box.
- In the Ignore libraries box, insert the library names (for example, Nafxcwd.lib;Libcmtd.lib).
Note The linker command-line equivalent in /NOD:<library name>.
- In the Object/library modules box, insert the library names. You must make sure that these are listed in order and as the first two libraries in the line (for example, Nafxcwd.lib Libcmtd.lib).
To set this option in Visual C++ .NET, read the “Setting Visual C++ Project Properties” online help topic.
Solution Two: Locate and Correct the Problem Module
To view the current library link order, follow these steps:
- On the Project menu, click Settings.
- In the Settings For view of the Project Settings dialog box, click to select the project configuration that is getting the link errors.
- On the Link tab, type /verbose:lib in the Project Options box.
- Rebuild your project. The libraries will be listed in the output window during the linking process.
STATUS
MORE INFORMATION
If the source file has a .c extension, or the file has a .cpp extension but does not use MFC, you can create and include a small header file (Forcelib.h) at the top of the module. This new header makes sure that thelibrary search order is correct.
Visual C++ does not contain this header file. To create this file, follow these steps:
- Open Msdev\Mfc\Include\Afx.h.
- Select the lines between #ifndef _AFX_NOFORCE_LIBS and #endif //!_AFX_NOFORCE_LIBS.
- Copy the selection to the Windows Clipboard.
- Create a new text file.
- Paste the contents of the Clipboard into this new file.
- Save the file as Msdev\Mfc\Include\Forcelib.h.
Steps to Reproduce the Problem in Visual C++ .NET
- Start Microsoft Visual Studio .NET.
- On the File menu, point to New, and then click Project.
- Click Visual C++ Projects under Project Types, and then click MFC Applicationunder Templates.
- In the Name text box, type Q148652.
- In the Location text box, type C:\Test, and then click OK.
- In the MFC Application Wizard dialog box, click Application Type.
- Click Dialog based under Application type, and then click Use MFC in a static library under Use of MFC.
- Click Finish.
- In Solution Explorer, under Source Files select all the three .cpp files.
- Right-click the three selected files, and then click Remove.
- Right-click Source files, point to Add, and then click Add New Item.
- Click C++ files under Templates. In the Name text box, type Aa. Click Open.
- Paste the following code in the Aa.cpp file:int test(){new int; return 1;}
- Right-click Source Files, point to Add, and then click Add Existing Item.
- Select the following files:
- Q148652.cpp
- Q148652Dlg.cpp
- stdafx.cpp
- Click Open.
- The files that you selected in step 15 appear under Source Files.
- Select all four .cpp files under Source Files.
- Right-click the four .cpp files that you selected, and then click Properties.
- Expand Configuration Properties, and then
http://libo.deng.blog.163.com/blog/static/40157422200851124138373/
__declspec(dllimport)的小秘密
邓立波 深圳,2008-6
作者联系方式:
email: libodeng@gmail.com
msn: libodeng@gmail.com
按照MSDN说明,当链接dll的导出函数时,只需要包含头文件和lib,__declspec(dllimport)修饰符不是必须的,但加上该修饰能使导出函数的调用效率更高。那么,究竟原因是什么?
假设dll导出了一个函数:
extern ”C” __declspec(dllexport) void fun();
如果程序中声明不加__declspec(dllimport),查看调用fun()函数的汇编代码:
004010AD call fun (004010d8)
其中fun被定义为一个标号(label),如下:
fun:
004010D8 jmp dword ptr [__imp__fun (0040e0e8)]
上面的符号__imp__fun指向的地址为fun()函数在exe中的导入节。
当声明加上__declspec(dllimport)后,查看调用fun()函数的汇编代码:
004010AB call dword ptr [__imp__fun (0040e0e8)]
从上面可以看出,加上__declspec(dllimport),编译器链接dll将省略一条jmp语句。
这是因为:
1。如果导出函数的声明没有用__declspec(dllimport) 修饰的话,编译器并不知道这个函数是由DLL导出的,所以编译器就把这个函数当作普通的外部引用来对待,产生一个外部引用的符号等着链接器解析。当链接器工作的时候,它是不能修改编译器生成的结果,所以会将该符号解析为对相应函数调入节的间接调用。
2。如果导出函数的声明用__declspec(dllimport) 修饰的话,编译器一开始就知道这个函数是DLL导出函数,直接编译成对调入节的调用。
|
我相信写WIN32程序的人,做过DLL,都会很清楚__declspec(dllexport)的作用,它就是为了省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话,你无法在DEF里指定导出的函数,只能用__declspec(dllexport)导出类。但是,MSDN文档里面,对于__declspec(dllimport)的说明让人感觉有点奇怪,先来看看MSDN里面是怎么说的:
初看起来,这段话前面的意思是,不用它也可以正常使用DLL的导出库,但最后一句话又说,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量这个是什么意思?? 那我就来试验一下,假定,你在DLL里只导出一个简单的类,注意,我假定你已经在项目属性中定义了 SIMPLEDLL_EXPORT #ifdef SIMPLEDLL_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
class DLL_EXPORT SimpleDLLClass
{
public:
SimpleDLLClass();
virtual ~SimpleDLLClass();
virtual getValue() { return m_nValue;};
private:
int m_nValue;
};
SimpleDLLClass.cpp #include "SimpleDLLClass.h"
SimpleDLLClass::SimpleDLLClass()
{
m_nValue=0;
}
SimpleDLLClass::~SimpleDLLClass()
{
}
然后你再使用这个DLL类,在你的APP中include SimpleDLLClass.h时,你的APP的项目不用定义 SIMPLEDLL_EXPORT 所以,DLL_EXPORT 就不会存在了,这个时候,你在APP中,不会遇到问题。这正好对应MSDN上说的__declspec(dllimport)定义与否都可以正常使用。但我们也没有遇到变量不能正常使用呀。 那好,我们改一下SimpleDLLClass,把它的m_nValue改成static,然后在cpp文件中加一行 int SimpleDLLClass::m_nValue=0; 如果你不知道为什么要加这一行,那就回去看看C++的基础。 改完之后,再去LINK一下,你的APP,看结果如何, 结果是LINK告诉你找不到这个m_nValue。明明已经定义了,为什么又没有了?? 肯定是因为我把m_nValue定义为static的原因。但如果我一定要使用Singleton的Design Pattern的话,那这个类肯定是要有一个静态成员,每次LINK都没有,那不是完了? 如果你有Platform SDK,用里面的Depend程序看一下,DLL中又的确是有这个m_nValue导出的呀。 #ifdef SIMPLEDLL_EXPORT #define DLL_EXPORT __declspec(dllexport) #else #define DLL_EXPORT __declspec(dllimport) #endif 再LINK,一切正常。原来dllimport是为了更好的处理类中的静态成员变量的,如果没有静态成员变量,那么这个__declspec(dllimport)无所谓 |
__declspec(dllimport)的作用就是保证上层的应用程序能够更好的引用该dll,通过定义__declspec(dllimport)上层应用可以知道函数、变量、类在什么地方定义,另外它也避免了在连接之前应用程序不知道导出的符号在什么地方定义,只能当做普通的外部引用来处理,从而造成编译过程中多于的代码。从本例和下一例当中可以知道他也能够保证接口类当中的静态成员的正确引用。
http://www.codeguru.com/forum/archive/index.php/t-221688.html
Click to See Complete Forum and Search –> : CString access violations!
I’m having access violation problems with CString. Checking the boards, I discovered that I do not suffer alone. A solution recommended was to type cast to LPCTSTR before assigning the new CString value.
I’m still getting access violation problems, though in a new spot. It now appears in the red-line in the following code.
void CString::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData)
{
AllocBeforeWrite(nSrcLen);
memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR));
GetData()->nDataLength = nSrcLen;
m_pchData[nSrcLen] = ”;
}
The strange thing is, that both before and after the memcpy, the m_pchData points to garbage. I did initialize it.
Hope this info isn’t too vague. Hope someone else has suffered through similar problems and has an idea.
Mat.
1) CString bug:
There is a bug in CString that prevents the correct synchronization of the reference counter which tracks which CString object actually owns the character array data.
Whenever you assign one CString object to another you assume it is copying the data when really it’s only referencing the original string and incrementing it’s reference counter.
If the original string is subsequently destroyed, and then the second string referencing the common data needs to access that data, sometimes it’s gone.
This leads to weird problems as you describe rather than a more predictable outcome that you might expect.
Example:
CString Original;
if(1)
{
CString LocalString = “My String”;
Original = LocalString; // causes LocalString reference to increase
}
// LocalString is now destroyed because it’s out of scope
printf(“This should work: %s\n”, Original);
There are occasions where the above will not work because the data has already been destroyed. This is a simple example that will likely work every time, but for sure there are some occasions this does not work (I don’t know the fundamental reason).
You can avoid this problem by casting:
Original = (LPCTSTR)LocalString;
This causes the Original CString object to copy the raw data from LocalString without playing with the references.
2) You’ve toasted the CString class
If you destroy CString, all instances of CString become mutually toasted, then it’s just a question of which one gets called next before things blow up (again, in an unpredictable way).
Example:
CString MyString “Hello World!”;
strcpy(MyString, “Oops, this is gonna leave a mark!”);
This causes very strange things to happen the next time you go to use any CString object (probably elsewhere in the code).
Therefore, you need to figure out which string assignment is causing you problems. This is an extremely tedious task that I’ve had to do myself a couple of times (so I feel your pain).
All I can say is that if you check and use casting every instance where you are using the CString equal operator, you will absolutely make this problem go away. Obviously problem 2 described above is just plain wrong, and should be fixed.
One of these days I might try and track down this nasty critter of a bug.
Hope this helps,
- Nigel
Hi,
I’m having access violation problems with CString. Checking the boards, I discovered that I do not suffer alone. A solution recommended was to type cast to LPCTSTR before assigning the new CString value.
I’m still getting access violation problems, though in a new spot. It now appears in the red-line in the following code.
void CString::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData)
{
AllocBeforeWrite(nSrcLen);
memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR));
GetData()->nDataLength = nSrcLen;
m_pchData[nSrcLen] = ”;
}
The strange thing is, that both before and after the memcpy, the m_pchData points to garbage. I did initialize it.
Hope this info isn’t too vague. Hope someone else has suffered through similar problems and has an idea.
Mat. This doesn’t mean there is a problem with CString. What you showed is just a symptom to a problem that may not have anything to do with CString. You can screw up any object if your coding is faulty, i.e. overwrite memory, calling delete twice on the same dynamic object, whatever. No class is invulnerable to coding that has memory bugs, faulty ccoding, or use undefined behavior. I highly doubt that if you are using CString correctly, CString would have a bug.
You need to show exactly how you are manipulating these CString objects in your program. Also, it would be helpful if you can mimic, with a very small, compilable example, of what you are doing with the CStrings and see if you can duplicate the error. There are too many posts that claim that CString has these bugs but always all of these problems have been found to be other aspects of the program that is affecting the CStrings. and not bugs with CString.
Regards,
Paul McKenzie
That is a good idea about writing a smaller version to duplicate the prob. I will get on that.
Oh, and btw, I DID do the LPCTSTR type-cast on all assignments for that string, and still I am having grief.
Thanks for the help,
I’ll be back with some code.
Mat.
Basically, the problem was caused by a little function like this one:
CNewInfo* CTheDocument::GetNewInfo()
{
CDataSet oDataSet;
m_DataMap.Lookup(m_strCurKey, oDataSet);
return &oDataSet.m_NewInfo;
}
As is now obviously the problem, oDataSet goes out of scope at the end of this function, and the pointer in the calling method is left pointing to an address which is no longer valid. What threw me for a loop was that I could manipulate the integer members of CNewInfo from the pointer in the calling function. Since re-assigning the CString member caused me grief, I automatically assumed he was the problem.
What I was trying to do by this function was to get a reference directly to the CNewInfo object in the DataMap, so that I wouldn’t have to do a SetAt() afterwards. But it seems that CMap is too tight of a class to let me be sloppy with it.
Out of curiosity though, would anyone know if it is possible to return a pointer to a members of a class using CMap as a template?
Thanks for the help. Maybe one day I’ll be able to give advice too.
Mat.
The thing you need to figure out is to make sure your map is not destroyed, i.e. it is a global or it is a member of an object that doesn’t go out of scope and destroyed. Then and only then can you return references to the object from a function.
Regards,
Paul McKenzie
Hope this info isn’t too vague. Hope someone else has suffered through similar problems and has an idea.I hope you don’t mind me commenting on one thing that I think was not clear. The code you posted appears to me to be MFC source code, not your source code. However that was not made clear.
There is a bug in CString that prevents the correct synchronization of the reference counter which tracks which CString object actually owns the character array data.I think many of us would be very interested in any such bugs. If such a bug exists I am quite surprised there is not more discussion of it. I agree with Paul that any such problems are probaly explained by improper use of CString and such things.
I saw no problems with the code as I checked all of these references, but after performing the casting trick, everthing worked. Ok, I might have missed something while I was checking, but I’d seen this problem before, and was looking for the culprit.
I am aware of several occasions where this problem has happened to others on my team, where after using the casting trick, everything worked fine.
Bizarre !
Anyway, all this hot air is going to force me to really get to the bottom of the issue the next time I see it (or think I see it
)
- Nigel
http://www.codeproject.com/KB/debug/mapfile.aspx
Introduction
Programming neat applications is one thing. But when a user informs you your software has crashed, you know it’s best to fix this before adding other features. If you’re lucky enough, the user will have a crash address. This will go a long way in solving the problem. But how can you determine what went wrong, using this crash address?
Creating a MAP file
Well first of all, you’ll need a MAP file. If you don’t have one, it will be nearly impossible to find where your application crashed using the crash address. So first, I’ll show you how to create a good MAP file. For this, I will create a new project (MAPFILE). You can do the same, or adjust your own project. I create a new project using the Win32 Application option in VC++ 6.0, selecting the ‘typical “Hello Word!” application’ to keep the size of the MAP file reasonable for explanation.
Once created we need to adjust the project settings for the release version. In the C/C++ tab, select “Line Numbers Only” for Debug Info.
Many people forget this, but you’ll need this option if you want a good MAP file. This will not affect your release in any way. Next is the Link tab. Here you need to select the “Generate mapfile” option. Also, type the switches /MAPINFO:LINES and /MAPINFO:EXPORTS in the Project Options edit box.
Now, you’re ready to compile and link your project. After linking, you will find a .map file in your intermediate directory (together with your exe).
Reading the MAP file
After all this dull work, now comes the neat part: how to read the MAP file. We’ll do this by using a crash example. So first: how to crash your application. I did this by adding these two lines at the end of the InitInstance() function:
char* pEmpty = NULL; *pEmpty = 'x'; // This is line 119
I’m sure you can find other instructions which will crash your application. Now recompile and link. If you start the application, it will crash and you’ll get a message like this: ‘The instruction at “0x004011a1″ referenced memory at “0×00000000″. The memory could not be “Written”.’ .
Now, it’s time to open the MAP file with notepad or something similar. You MAP file will look like this:
The top of the MAP file contains the module name, the timestamp indicating the link of the project, and the preferred load address (which will probably be 0x00400000 unless you’re using a dll). After the header comes the section information that shows which sections the linker brought in from the various OBJ and LIB files.
MAPFILE Timestamp is 3df6394d (Tue Dec 10 19:58:21 2002) Preferred load address is 00400000 Start Length Name Class 0001:00000000 000038feH .text CODE 0002:00000000 000000f4H .idata$5 DATA 0002:000000f8 00000394H .rdata DATA 0002:0000048c 00000028H .idata$2 DATA 0002:000004b4 00000014H .idata$3 DATA 0002:000004c8 000000f4H .idata$4 DATA 0002:000005bc 0000040aH .idata$6 DATA 0002:000009c6 00000000H .edata DATA 0003:00000000 00000004H .CRT$XCA DATA 0003:00000004 00000004H .CRT$XCZ DATA 0003:00000008 00000004H .CRT$XIA DATA 0003:0000000c 00000004H .CRT$XIC DATA 0003:00000010 00000004H .CRT$XIZ DATA 0003:00000014 00000004H .CRT$XPA DATA 0003:00000018 00000004H .CRT$XPZ DATA 0003:0000001c 00000004H .CRT$XTA DATA 0003:00000020 00000004H .CRT$XTZ DATA 0003:00000030 00002490H .data DATA 0003:000024c0 000005fcH .bss DATA 0004:00000000 00000250H .rsrc$01 DATA 0004:00000250 00000720H .rsrc$02 DATA
After the section information, you get the public function information. Notice the “public” part. If you have static-declared C functions, they won’t show up in the MAP file. Fortunately, the line numbers will still reflect the static functions. The important parts of the public function information are the function names and the information in the Rva+Base column, which is the starting address of the function.
Address Publics by Value Rva+Base Lib:Object 0001:00000000 _WinMain@16 00401000 f MAPFILE.obj 0001:000000c0 ?MyRegisterClass@@YAGPAUHINSTANCE__@@@Z 004010c0 f MAPFILE.obj 0001:00000150 ?InitInstance@@YAHPAUHINSTANCE__@@H@Z 00401150 f MAPFILE.obj 0001:000001b0 ?WndProc@@YGJPAUHWND__@@IIJ@Z 004011b0 f MAPFILE.obj 0001:00000310 ?About@@YGJPAUHWND__@@IIJ@Z 00401310 f MAPFILE.obj 0001:00000350 _WinMainCRTStartup 00401350 f LIBC:wincrt0.obj 0001:00000446 __amsg_exit 00401446 f LIBC:wincrt0.obj 0001:0000048f __cinit 0040148f f LIBC:crt0dat.obj 0001:000004bc _exit 004014bc f LIBC:crt0dat.obj 0001:000004cd __exit 004014cd f LIBC:crt0dat.obj 0001:00000591 __XcptFilter 00401591 f LIBC:winxfltr.obj 0001:00000715 __wincmdln 00401715 f LIBC:wincmdln.obj //SNIPPED FOR BETTER READING 0003:00002ab4 __FPinit 00408ab4 <common> 0003:00002ab8 __acmdln 00408ab8 <common> entry point at 0001:00000350 Static symbols 0001:000035d0 LeadUp1 004045d0 f LIBC:memmove.obj 0001:000035fc LeadUp2 004045fc f LIBC:memmove.obj //SNIPPED FOR BETTER READING 0001:00000577 __initterm 00401577 f LIBC:crt0dat.obj 0001:0000046b _fast_error_exit 0040146b f LIBC:wincrt0.obj
The public function part is followed by the line information (you got this if you used the /MAPINFO:LINES in the Link tab and selected the “Line numbers” in the C/C++ tab). After this, you will get the export information if your project contains exported functions and you included /MAPINFO:EXPORTS in the link tab.
Line numbers for .\Release\MAPFILE.obj(F:\MAPFILE\MAPFILE.cpp) segment .text
24 0001:00000000 30 0001:00000004 31 0001:0000001b 32 0001:00000027
35 0001:0000002d 53 0001:00000041 40 0001:00000047 43 0001:00000050
45 0001:00000077 47 0001:00000088 48 0001:0000008f 52 0001:000000ad
53 0001:000000b3 71 0001:000000c0 80 0001:000000c3 81 0001:000000c8
82 0001:000000ff 86 0001:00000114 88 0001:00000135 89 0001:00000145
102 0001:00000150 108 0001:00000155 110 0001:00000188 122 0001:0000018d
115 0001:0000018e 116 0001:0000019a 119 0001:000001a1 121 0001:000001a8
122 0001:000001ae 135 0001:000001b0 143 0001:000001cc 172 0001:000001ee
175 0001:0000020d 149 0001:00000216 157 0001:0000022c 175 0001:00000248
154 0001:00000251 174 0001:0000025f 175 0001:00000261 151 0001:0000026a
174 0001:00000287 175 0001:00000289 161 0001:00000294 164 0001:000002a8
165 0001:000002b6 166 0001:000002d8 174 0001:000002e7 175 0001:000002e9
169 0001:000002f2 174 0001:000002fa 175 0001:000002fc 179 0001:00000310
186 0001:0000031e 193 0001:0000032e 194 0001:00000330 188 0001:00000333
183 0001:00000344 194 0001:00000349
Now we will look up where the crash occurred. First, we’ll determine which function contains the crash address. Look in the “Rva+Base” column and search the first function with an address bigger than the crash address. The preceding entry in the MAP file is the function that had the crash. In our example our crash address is 0x004011a1. This is between 0x00401150 and 0x004011b0 so we know the crash function is ?InitInstance@@YAHPAUHINSTANCE__@@H@Z . Any function name that starts with a question mark is a C++ decorated name. To translate the name, pass it as a command-line parameter to the Platform SDK program UNDNAME.EXE (in the bin dir). You won’t need to do this most of the time as you might figure it out just by looking at it (here: InitInstance() in MAPFILE.obj).
This is a big step for bug tracking. But it gets even better: we can find out on which line the crash occurred! We need to do some basic hexadecimal mathematics, so people whom can’t do this without a calculator: now is the time to use it. The first step is the following calculation: crash_address - preferred_load_address - 0x1000
Addresses are offsets from the beginning of the first code section, se we need to do this calculation. Subtracting the preferred load address is logical, but why do we need to substract another 0×1000? The crash address is an offset from the beginning of the code section, but the first part of the binary isn’t the code section! The first part of the binary is the Portable Executable (PE), which is 0×1000 bytes long. Mystery solved. In our example, this is: 0x004011a1 - 0x00400000 - 0x1000 = 0x1a1
Now it’s time to look in the line information section of the MAP file. The lines are shown like this: 30 0001:00000004. The first number is the line number, the second number is the offset from the beginning of the code section in which this line occurred. If we want to look for our line number, we just have to do the same thing we did for the function: determine the first occurrence of a bigger offset than the one we just calculated. The crash occurred in the preceding entry. In our example: 0x1a1 is before 0x1a8. So our crash occurred on line 119 in MAPFILE.CPP.
Keeping track of MAP files
Each release had it’s own MAP file. It’s not a bad idea to include the MAP file with the exe distribution. This way, you can be certain you have the correct MAP file for this exe. You could keep every MAP file with every exe on your system, but we all know this might give some troubles later on. The MAP file doesn’t contain any information you wouldn’t want the user to see (unless maybe class and function names ?) . A user would have no use with it, but at least you can ask for the MAP file if you don’t have a copy yourself.
Acknowledgements
John Robbins for his “Debugging Applications” book
http://www.codeproject.com/KB/debug/crash_report.aspx

Figure 1 – Main Dialog
Contents
- Overview
- Usage
- Using the CrashRpt Library in Your Application
- Using the Crash Report
- Deployment
- References and Related Links
- Change History
Overview
If you’ve ever been tasked with debugging a fatal exception, you probably know how difficult it can be given only the user’s steps to reproduce. Many factors such as the application’s version, user’s operating system, and dependent modules may contribute to the eventual crash. This makes duplicating the user’s environment and thereby the crash, nearly impossible for all but the most obvious bugs.
The CrashRpt library is a light weight error handling framework. This module will intercept any unhandled exception generated by your application, build a complete debug report, and optionally, mail the report to you.
Usage
In this article, a crash report refers to a collection of files intended to help the developer quickly diagnose the cause of a crash. Specifically the crash report includes a minidump, a crash log and up to ten additional application specific files supplied by the application via a crash callback. Of these the most useful will most likely be the application minidump. The minidump contains the call stack, local variables, details all of your application’s modules, and can even help pinpoint the source line number that generated the exception.
The CrashRpt DLL works like the new Dr. Watson utility that ships with XP. It intercepts unhandled exceptions, creates a minidump, builds a crash log, presents an interface to allow the user to review the crash report, and finally it compresses and optionally emails the crash report back to you.
When an unhandled exception is detected, CrashRpt notifies the user and allows them to review the report contents. First the main dialog shown above is displayed. From here the user can enter their comments and email address or review the report contents by clicking the hyperlink. This takes them to the details dialog, where they are presented with the files that make up the report. Double clicking on the filename will open that file in its associated program, if an association exists for that file type.

Figure 2 – Crash Details Dialog
Once the user is satisfied, he may close the details dialog and click the ‘Send’ button on the main dialog, which will email his complete crash report to you.
Using the CrashRpt library in Your Application
Building the library
Download and unzip the source code attached to this article. Open the complete.dsw workspace located in the top level directory. This workspace contains the source for the two sample applications and the CrashRpt library. You only need to build the CrashRpt project – crashrpt.dsp.
Notes:
- CrashRpt library uses the WTL for its GUI components, so WTL must be installed and properly configured in order to build the CrashRpt library. You can download the WTL here. If you’re new to the WTL then CodeProject has some article here, and there is a good starting guide here.
- CrashRpt library uses Microsoft’s Debug Help library (dbghelp.dll). The proper h and lib files must be installed and properly configured in order to build the CrashRpt library. These files are available in the Debugging SDK, which can be downloaded here (be sure to select the SDK setup option).
After the build completes, you should end up with the following:
crashrpt\include crashrpt.h crashrpt\bin\debug or crashrpt\bin\release crashrpt.dll crashrpt\lib crashrpt.lib crashrpt\src [all source files...]
Linking against the library
To implicitly link against the library, you need to include the crashrpt.h file and link in the crashrpt.lib file. I find it easiest to add the following two lines to my main application file.
#include "[whateveryourpath]/crashrpt/include/crashrpt.h" #pragma comment(lib, "[whateveryourpath]/crashrpt/lib/crashrpt")
Figure 3 – Integrating CrashRpt with your application
Initializing the library
The library needs to be initialized before it will catch any exceptions. You do this by calling the Install method, usually from your main function. The Install method is detailed below.
//----------------------------------------------------------------------------- // Install // Initializes the library and optionally set the client crash callback and // set up the email details. // // Parameters // pfn Client crash callback // lpTo Email address to send crash report // lpSubject Subject line to be used with email // // Return Values // If the function succeeds, the return value is a pointer to the underlying // crash object created. This state information is required as the first // parameter to all other crash report functions. // // Remarks // Passing NULL for lpTo will disable the email feature and cause the crash // report to be saved to disk. // CRASHRPTAPI LPVOID Install( IN LPGETLOGFILE pfn OPTIONAL, // client crash callback IN LPCTSTR lpTo OPTIONAL, // Email:to IN LPCTSTR lpSubject OPTIONAL // Email:subject );
Figure 4 – The Install() function
All of the parameters are optional. The first parameter is a pointer to a crash callback function defined as:
// Client crash callback typedef BOOL (CALLBACK *LPGETLOGFILE) (LPVOID lpvState);
Figure 5 – Client crash callback
You would define this callback only if you wanted to be notified of an application failure, so that you could perform some basic clean up (i.e. close db connections, attempt to save, etc). Otherwise, you can simply pass NULL for this parameter.
The second parameter defines the email address you want the crash report mailed to, or NULL if you prefer the reports be saved to the user’s workstation.
The third parameter is the subject line used in the generated mail message.
The Install function returns a pointer to the underlying object that implements the real functionality of this library. This value is required for all subsequent calls into the library.
If, after you have called Install, you decide to unhook the CrashRpt library, you would call Uninstall.
//----------------------------------------------------------------------------- // Uninstall // Uninstalls the unhandled exception filter set up in Install(). // // Parameters // lpState State information returned from Install() // // Return Values // void // // Remarks // This call is optional. The crash report library will automatically // deinitialize when the library is unloaded. Call this function to // unhook the exception filter manually. // CRASHRPTAPI void Uninstall( IN LPVOID lpState // State from Install() );
Figure 6 – The Uninstall function
You would only ever call Uninstall if you decided, after calling Install, that you did not want the CrashRpt library to intercept exceptions. So, basically you will probably never call this method directly.
Adding custom files to the report
The client application can, at any time, supply files to be included in the crash report by calling the AddFile function.
//----------------------------------------------------------------------------- // AddFile // Adds a file to the crash report. // // Parameters // lpState State information returned from Install() // lpFile Fully qualified file name // lpDesc Description of file, used by details dialog // // Return Values // void // // Remarks // This function can be called anytime after Install() to add one or more // files to the generated crash report. // CRASHRPTAPI void AddFile( IN LPVOID lpState, // State from Install() IN LPCTSTR lpFile, // File name IN LPCTSTR lpDesc // File desc );
Figure 7 – The AddFile function
This is useful when your application uses or produces external files such as initialization files or log files. When a report is generated, it will include these additional files.
Manually generating a report
You can force report generation by calling the GenerateErrorReport. This is useful if you want to provide an easy way to gather debugging information about your application to help debug a non-fatal bug.
//----------------------------------------------------------------------------- // GenerateErrorReport // Generates the crash report. // // Parameters // lpState State information returned from Install() // pExInfo Pointer to an EXCEPTION_POINTERS structure // // Return Values // void // // Remarks // Call this function to manually generate a crash report. // CRASHRPTAPI void GenerateErrorReport( IN LPVOID lpState, IN PEXCEPTION_POINTERS pExInfo OPTIONAL );
Figure 8 – The GenerateErrorReport function
If you do not supply a valid EXCEPTION_POINTERS, structure the minidump callstack may be incomplete.
Generate debug symbols
To get the most out of the minidump, the debugger needs your application’s debug symbols. By default release builds don’t generate debug symbols. You can configure VC to generate debug symbols for release builds by changing a couple of project settings.
With the release build configuration selected, on the C/C++ tab under the General category, select ‘Program Database’ under Debug info.

Figure 9 – C++ Project Settings
On the Link tab under the General category, check the ‘Generate debug info’ option.

Figure 9 – Link Project Settings
Now release builds will generate debug symbols in a PDB file. Keep all executables and PDB files for each release that ships to customers. You will need these files to read minidump files in the debugger.
Using the Crash Report
Using the Crash Log
The crash log is an XML file that describes details about the crash including the type of exception, the module and offset where the exception occurred, as well as some cursory operating system and hardware information. I wrote the crash log to make it easier to catalog crashes. A crash can be uniquely identified by the module, offset and exception code. This information could be inspected by a developer or an automated process, and compared against previously reported problems. If a match is found, the developer or automated process, could inform the user of the solution without having to debug the error again.
The log is divided into four different sections or nodes. The first node is ExceptionRecord we discussed earlier.

Figure 10 – ExceptionRecord Node
Next is the Processor node, which contains a little information about the user’s CPU.

Figure 11 – Process Node
Next is the OperatingSystem node, which contains the user’s operating system version information.

Figure 12 – OperatingSystem Node
Last is the Modules node. This node contains the path, version, base address, size, and time stamp for every module loaded by the deceased application.

Figure 13 – Modules Node
Using the Crash Dump File
The crash dump file is a minidump created with the help of the DbgHelp DLL’s MiniDumpWriteDump function. The minidump contains various information about the state of the application when the error occurred including the call stack, local variables, and loaded modules. For more on creating minidumps, check out Andy Pennell’s article.
You can view minidump files in VS.NET or the WinDbg debugger. Because WinDbg is free, I’ll use it in the following example. You can download WinDbg from here. I’m using version 6.1.0017.0 in the example.
The sample application included with this article does nothing but generate a null pointer exception. I’ll use the sample to generate a crash and demonstrate how to use the resulting minidump.
When you run the sample application, click on the bomb button to generate a null pointer exception, and save the resulting crash report. Then extract the crash.dmp file from the crash report, launch WinDbg, and open the crash dump by pressing CTRL+D.
Next, you need to set the symbol path for WinDbg with the .sympath command. Switch to the command window (ALT+1) and enter .sympath followed by a space followed by the semi-colon delimited list of directories to search.
.sympath c:\downloads\CrashRptTest
Figure 14 – Setting the symbol path
Similarly you need to set the executable and source search paths with the .exepath and .srcpath commands.
.exepath c:\downloads\CrashRptTest .srcpath c:\downloads\CrashRptTest
Figure 15 – Setting the source and executable paths
The final step is to change the debugger context to the context record associated with the exception by entering the .ecxr command.
.ecxr
Figure 15 – Setting the exception context record
If everything is configured correctly, you should now be able to walk the call stack, see local variables, and loaded modules. You can even have WinDbg highlight the offending line of code by double clicking the CrashRptTest frame in the Call Stack window (ALT+6). Note: The exact line number may be a little off due to linker optimizations.

Figure 16 – The Promised Land: Using WinDbg to Locate the Cause of a Null Pointer Exception
Deployment
The CrashRpt library relies on a couple of redistributable libraries. To be sure the library has access to the required files, you can distribute the ZLib and DbgHelp I’ve included.
| Library | File Version | Description |
| CrashRpt.DLL | 3.0.2.1 | Crash report library |
| ZLib.DLL | 1.1.3.0 | ZLib compression library |
| DbgHlp.DLL | 6.1.17.1 | Microsoft debug help library |
Figure 17 – Redistributable Libraries
What to ship and what to save
As I mentioned earlier, to debug a crash you need not only the minidupmp file, but also the symbol and executable files that make up your application. When preparing a build to be released to clients, you should always save the exact executable modules you ship to clients, along with the corresponding debug symbols. This way when a crash report comes in, you will have the modules and debug symbols that the debugger will need to properly interpret the minidump.
I’ve received several comments/inquiries about shipping debug builds or debug symbols. You should never ship debug builds or debug symbols as they will not only take up more space on your CD/download/client’s workstation, but they will also make reverse engineering your code a trivial exercise. To be clear, what I’m suggesting is modify your release build configuration so that it generates debug symbols, saving both the release builds of your modules and their corresponding debug symbols in your source control system and delivering only the release builds of your modules to clients (as you do today). When a crash report comes in, you use the release build and debug symbols you archived, along with the minidump included in the crash report, to debug the crash.
Note: CrashRpt uses Microsoft’s Debug Help library (dbghelp.dll). This library was shipped with Windows XP, but certain versions are redistributable. I recommend you to install the dbghelp.dll file, included in the source/demo attachments, along the crashrpt.dll into your application’s directory to avoid the possible conflict or missing dependency issues..
A word about preferred base load addresses
Every executable module (EXE, DLL, OCX, whatever) has a preferred base load address. This is the address in the application’s process space that the loader will try to map that module. If two or more modules list the same base load address, the loader will be forced to relocate the modules until each module loads at a unique address. Not only does this slow down the start up time of your application, but it also makes it impossible to debug fatal exceptions. In order to use the minidump file, you must ensure that your application’s modules do not collide. You can use rebase.exe or manually override the preferred base load address for each conflicting module. Either way you need to make sure that your application modules always load at the same address for the minidump file to be useful. You can find more information about this in John Robbin’s April 1998 MSJ column.
References
For additional information about topics directly related to this article, see the links below:
- Debugging Tools for Windows.
- WTL Download.
- ZLib Compression Library.
- Reading Minidump Files with VS.NET.
- John Robbins on Crash Dumps.
- April 1998 MSJ Bugslayer Column.
- WTL Reference.
Change History
- 03/17/2003Major Changes.
- Replaced MFC with WTL.
- Changed crashrpt interface.
- Major refactoring.
- Updated article.
Minor Changes.
- Details dialog preview window now uses system defined window color instead of white.
- Directory structure not saved in ZIP.
Bugs Fixed
- Support for use by multiple apps.
- Buffer overrun error when previewing files > 32k.
- Main dialog now displays app icon.
- 01/12/2003
- Initial release.
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
http://www.codeproject.com/KB/debug/cdbntsd4.aspx
Introduction
Welcome to the fourth installment of this debugging series. In this installment, we will be getting a bit away from actual debugging for a bit and dive into creating helpful debugging aids. I definitely would never condone writing debugging aids just to write them. I would find a reason to write something, perhaps something you do all the time that gets tedious. Once you have found something you would love to automate, I would then see how you could automate it.
This is what I have done. During my debugging escapades, I always search the stack and other locations for strings. Why do I do this? People are not computers, we understand language rather than numbers. This being the case, a lot of applications and even drivers are written based upon strings. I would not say everything is a string, but there’s usually a string somewhere. If you think about it, you really don’t need strings at all. We could all just use numbers and never use another string again. Some may think well, when you expose a UI of course, you’re going to eventually run into a string somewhere… Well, doesn’t have to be that way, now does it? I mean, I’m not even talking about just User Interfaces, I’m talking about the guts of programs that aren’t even exposed to the user at all. Programmers are still human and like to talk in some language beyond binary. This being the case, even the internals of applications sometimes use strings to represent things. You can find strings just about anywhere, even in drivers.
So There’re Strings, So What?
Well, they provide an added level of readability to an application. This is the first rule of thumb, perhaps if you could find a string somewhere on the stack, you could better track down what the application is doing. These strings could be environment strings, file names, device names (COM1, \Device\xxxx, etc.), names of other objects, user names, GUIDs, etc. This information can better help track down what the application is doing and where it could be in your program.
Another interesting concept is, I don’t know this for a fact, but it’s my belief that the most common buffer overruns are due to invalid string manipulations. Whether it be forgetting the NULL character, or misjudging allocation since an API returns # of characters and not # of bytes. If a string overwrote memory in your program and you can find the string, it’s a lot easier to track down who created it.
Where do we start?
I start on the stack. I have a trap, the first thing I do after “KB” and “DDS ESP” would then be to use “DC ESP”. This command dumps the DWORDs on one side and the printable characters on the other. Let’s see an example of this:
0:000> dc esp 0006febc 77d43a09 77d43c7d 0006fefc 00000000 .:.w}<.w........ 0006fecc 00000000 00000000 00000000 0006ff1c ................ 0006fedc 010028e4 0006fefc 00000000 00000000 .(.............. 0006feec 00000000 00000000 77e7ad86 00091ee7 ...........w.... 0006fefc 001a03e4 00000118 0000ffff bf8a75ed .............u.. 0006ff0c 0768a2ca 00000229 00000251 00000000 ..h.)...Q....... 0006ff1c 0006ffc0 01006c54 01000000 00000000 ....Tl.......... 0006ff2c 00091ee7 0000000a 00000000 00000000 ................ 0:000> dc 0006ff3c 7ffdf000 80543940 f544fc5c 00000044 ....@9T.\.D.D... 0006ff4c 00092b28 00092b48 00092b70 00000000 (+..H+..p+...... 0006ff5c 00000000 00000000 00000000 00000000 ................ 0006ff6c 00000000 00000000 00000000 00000000 ................ 0006ff7c 00000000 ffffffff ffffffff ffffffff ................ 0006ff8c 00091ee7 00000000 00000001 00272620 ............ &'. 0006ff9c 00272d00 00000000 00000000 0006ff34 .-'.........4... 0006ffac e24296d0 0006ffe0 01006d14 01001888 ..B......m...... 0:000> 0006ffbc 00000000 0006fff0 77e814c7 00000000 ...........w.... 0006ffcc 00000000 7ffdf000 f544fcf0 0006ffc8 ..........D..... 0006ffdc 80534504 ffffffff 77e94809 77e91210 .ES......H.w...w 0006ffec 00000000 00000000 00000000 01006ae0 .............j.. 0006fffc 00000000 78746341 00000020 00000001 ....Actx ....... 0007000c 00000654 0000007c 00000000 00000020 T...|....... ... 0007001c 00000000 00000014 00000001 00000003 ................ 0007002c 00000034 000000ac 00000001 00000000 4............... 0:000> 0007003c 00000000 00000000 00000000 00000000 ................ 0007004c 00000002 00000000 00000000 00000000 ................ 0007005c 00000168 00000190 00000000 2d59495b h...........[IY- 0007006c 000002f8 00000032 0000032c 000002b8 ....2...,....... 0007007c 00000010 00000002 0000008c 00000002 ................ 0007008c 00000001 000000ac 00000538 00000001 ........8....... 0007009c 00000002 000005e4 00000070 00000001 ........p....... 000700ac 64487353 0000002c 00000001 00000001 SsHd,........... 0:000> 000700bc 00000003 00000002 0000008c 00000001 ................ 000700cc 00000000 0000002c 0000005e 0000005e ....,...^...^... 000700dc 00000000 00000000 00000000 00000000 ................ 000700ec 00000000 00000000 00000000 00000000 ................ 000700fc 00000000 00000002 00000028 00000034 ........(...4... 0007010c 003a0043 0057005c 004e0049 004f0044 C.:.\.W.I.N.D.O. 0007011c 00530057 0030002e 0057005c 006e0069 W.S...0.\.W.i.n. 0007012c 00780053 005c0073 00000000 00000000 S.x.s.\.........
I started notepad.exe and just broke into it, then dumped the stack of the primary (and only) thread. The strings on the stack are "local arrays", such as declaring char x[10]; in your function. These aren't the only strings in a program though. There are others and these others are stored in pointers that are declared as local variables and even passed to functions such as CreateFile. The first parameter of CreateFile takes a string.
So, what do I usually do? I then search the stack for memory locations which could be strings and I then do "DC" again on them or "DA" for Dump ANSI string or "DU" for Dump Unicode String. The problem with this is that it's slow and tedious. I could not find any debugger command to do this for me (if there is one, let me know), so I ended up writing my own.
Writing Your Own?
WINDBG supports DLLs created by anyone as long as they export functions and behave in a manner defined by Microsoft. This means you can write PLUGINS! It used to be that people would write plug-ins to !<mydatastructure> <address> which basically dumped the members of their data structure along with the names. However, WINDBG supports the "dt" command that if you have a PDB (symbol file) it can do this for you without writing any code! Let's see an example of this.
Using the "dt <yourdll>!<your data structure>" will dump the structure's content along with their names. Let's look at a quick example.
0:000> dt ntdll!_PEB +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 SpareBool : UChar +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void +0x00c Ldr : Ptr32 _PEB_LDR_DATA +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS +0x014 SubSystemData : Ptr32 Void +0x018 ProcessHeap : Ptr32 Void +0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION +0x020 FastPebLockRoutine : Ptr32 Void +0x024 FastPebUnlockRoutine : Ptr32 Void +0x028 EnvironmentUpdateCount : Uint4B +0x02c KernelCallbackTable : Ptr32 Void +0x030 SystemReserved : [1] Uint4B +0x034 ExecuteOptions : Pos 0, 2 Bits +0x034 SpareBits : Pos 2, 30 Bits +0x038 FreeList : Ptr32 _PEB_FREE_BLOCK +0x03c TlsExpansionCounter : Uint4B +0x040 TlsBitmap : Ptr32 Void +0x044 TlsBitmapBits : [2] Uint4B +0x04c ReadOnlySharedMemoryBase : Ptr32 Void +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void +0x058 AnsiCodePageData : Ptr32 Void +0x05c OemCodePageData : Ptr32 Void +0x060 UnicodeCaseTableData : Ptr32 Void +0x064 NumberOfProcessors : Uint4B +0x068 NtGlobalFlag : Uint4B +0x070 CriticalSectionTimeout : _LARGE_INTEGER +0x078 HeapSegmentReserve : Uint4B +0x07c HeapSegmentCommit : Uint4B +0x080 HeapDeCommitTotalFreeThreshold : Uint4B +0x084 HeapDeCommitFreeBlockThreshold : Uint4B +0x088 NumberOfHeaps : Uint4B +0x08c MaximumNumberOfHeaps : Uint4B +0x090 ProcessHeaps : Ptr32 Ptr32 Void +0x094 GdiSharedHandleTable : Ptr32 Void +0x098 ProcessStarterHelper : Ptr32 Void +0x09c GdiDCAttributeList : Uint4B +0x0a0 LoaderLock : Ptr32 Void +0x0a4 OSMajorVersion : Uint4B +0x0a8 OSMinorVersion : Uint4B +0x0ac OSBuildNumber : Uint2B +0x0ae OSCSDVersion : Uint2B +0x0b0 OSPlatformId : Uint4B +0x0b4 ImageSubsystem : Uint4B +0x0b8 ImageSubsystemMajorVersion : Uint4B +0x0bc ImageSubsystemMinorVersion : Uint4B +0x0c0 ImageProcessAffinityMask : Uint4B +0x0c4 GdiHandleBuffer : [34] Uint4B +0x14c PostProcessInitRoutine : Ptr32 +0x150 TlsExpansionBitmap : Ptr32 Void +0x154 TlsExpansionBitmapBits : [32] Uint4B +0x1d4 SessionId : Uint4B +0x1d8 AppCompatFlags : _ULARGE_INTEGER +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER +0x1e8 pShimData : Ptr32 Void +0x1ec AppCompatInfo : Ptr32 Void +0x1f0 CSDVersion : _UNICODE_STRING +0x1f8 ActivationContextData : Ptr32 Void +0x1fc ProcessAssemblyStorageMap : Ptr32 Void +0x200 SystemDefaultActivationContextData : Ptr32 Void +0x204 SystemAssemblyStorageMap : Ptr32 Void +0x208 MinimumStackCommit : Uint4B 0:000>
We just dumped out the structure information for the _PEB structure defined by Windows. We will now attempt to find our PEB and dump this address.
0:000> !teb
TEB at 7ffde000
ExceptionList: 0006ffb0
StackBase: 00070000
StackLimit: 0005f000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffde000
EnvironmentPointer: 00000000
ClientId: 00000b80 . 00000f40
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffdf000
LastErrorValue: 0
LastStatusValue: c0000034
Count Owned Locks: 0
HardErrorMode: 0
0:000> dt ntdll!_PEB 7ffdf000
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 SpareBool : 0 ''
+0x004 Mutant : 0xffffffff
+0x008 ImageBaseAddress : 0x01000000
+0x00c Ldr : 0x00191ea0
+0x010 ProcessParameters : 0x00020000
+0x014 SubSystemData : (null)
+0x018 ProcessHeap : 0x00090000
+0x01c FastPebLock : 0x77fc49e0
+0x020 FastPebLockRoutine : 0x77f5b2a0
+0x024 FastPebUnlockRoutine : 0x77f5b380
+0x028 EnvironmentUpdateCount : 1
+0x02c KernelCallbackTable : 0x77d42a38
+0x030 SystemReserved : [1] 0
+0x034 ExecuteOptions : 0y00
+0x034 SpareBits : 0y000000000000000000000000000000 (0)
+0x038 FreeList : (null)
+0x03c TlsExpansionCounter : 0
+0x040 TlsBitmap : 0x77fc4680
+0x044 TlsBitmapBits : [2] 0x7ff
+0x04c ReadOnlySharedMemoryBase : 0x7f6f0000
+0x050 ReadOnlySharedMemoryHeap : 0x7f6f0000
+0x054 ReadOnlyStaticServerData : 0x7f6f0688 -> (null)
+0x058 AnsiCodePageData : 0x7ffb0000
+0x05c OemCodePageData : 0x7ffc1000
+0x060 UnicodeCaseTableData : 0x7ffd2000
+0x064 NumberOfProcessors : 1
+0x068 NtGlobalFlag : 0x70
+0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
+0x078 HeapSegmentReserve : 0x100000
+0x07c HeapSegmentCommit : 0x2000
+0x080 HeapDeCommitTotalFreeThreshold : 0x10000
+0x084 HeapDeCommitFreeBlockThreshold : 0x1000
+0x088 NumberOfHeaps : 5
+0x08c MaximumNumberOfHeaps : 0x10
+0x090 ProcessHeaps : 0x77fc5a80 -> 0x00090000
+0x094 GdiSharedHandleTable : 0x00360000
+0x098 ProcessStarterHelper : (null)
+0x09c GdiDCAttributeList : 0x14
+0x0a0 LoaderLock : 0x77fc1774
+0x0a4 OSMajorVersion : 5
+0x0a8 OSMinorVersion : 1
+0x0ac OSBuildNumber : 0xa28
+0x0ae OSCSDVersion : 0x100
+0x0b0 OSPlatformId : 2
+0x0b4 ImageSubsystem : 2
+0x0b8 ImageSubsystemMajorVersion : 4
+0x0bc ImageSubsystemMinorVersion : 0
+0x0c0 ImageProcessAffinityMask : 0
+0x0c4 GdiHandleBuffer : [34] 0
+0x14c PostProcessInitRoutine : (null)
+0x150 TlsExpansionBitmap : 0x77fc4660
+0x154 TlsExpansionBitmapBits : [32] 0
+0x1d4 SessionId : 0
+0x1d8 AppCompatFlags : _ULARGE_INTEGER 0x0
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER 0x0
+0x1e8 pShimData : (null)
+0x1ec AppCompatInfo : (null)
+0x1f0 CSDVersion : _UNICODE_STRING "Service Pack 1"
+0x1f8 ActivationContextData : 0x00080000
+0x1fc ProcessAssemblyStorageMap : 0x000929a8
+0x200 SystemDefaultActivationContextData : 0x00070000
+0x204 SystemAssemblyStorageMap : (null)
+0x208 MinimumStackCommit : 0
0:000>
If you remember correctly, the !teb gives you the "thread environment block". A part of this block will show you the PEB for a process. Now, you see that not only does it print the information contained in the data structure, it also knows how to display the information based upon the type of data. Why did I show you this? This is because of what I specified above. The first thing people do or want to do is start to write debug extensions to dump all their data structures and this is not feasible anymore! You then have to keep changing your debug code every time you add or move information around. It's best to use the "dt" command and painlessly find all the data contained in any of your internal data structures.
Starting To Write The Extension
So, my proposed extension is !dumpstrings. This will go through a memory location and dump all the pointers. I can then do !dumpstrings esp and dump the strings to all pointers on the stack.
First, let's start with how you write the extension. WINDBG requires you at least export two functions. These functions in my source are posted below:
/*********************************************************** * ExtensionApiVersion * * Purpose: WINDBG will call this function to get the version * of the API * * Parameters: * Void * * Return Values: * Pointer to a EXT_API_VERSION structure. * ***********************************************************/ LPEXT_API_VERSION WDBGAPI ExtensionApiVersion (void) { return &g_ExtApiVersion; } /*********************************************************** * WinDbgExtensionDllInit * * Purpose: WINDBG will call this function to initialize * the API * * Parameters: * Pointer to the API functions, Major Version, Minor Version * * Return Values: * Nothing * ***********************************************************/ VOID WDBGAPI WinDbgExtensionDllInit (PWINDBG_EXTENSION_APIS lpExtensionApis, USHORT usMajorVersion, USHORT usMinorVersion) { ExtensionApis = *lpExtensionApis; }
The first function, ExtensionApiVersion, simply returns a version, and all we do is supply version numbers that WINDBG would expect to make it happy. Here is the declaration of g_ExtApiVersion.
/*********************************************************** * Global Variable Needed For Versioning ***********************************************************/ EXT_API_VERSION g_ExtApiVersion = { 5 , 5 , EXT_API_VERSION_NUMBER , 0 } ;
The EXT_API_VERSION_NUMBER is declared in wdbgexts.h. Please note that there are other variations of debugger extension DLLs, such as using ntsdexts.h. I am going over just one simple example that works with the current CDB and WinDbg debuggers that you can download off of Microsoft's web site. I am also using windbgexts.h, not ntsdexts.h. If you look at your SDK include files, you will notice you have both headers.
So, how is EXT_API_VESRION_NUMBER declared? On my system, it is declared as:
#define EXT_API_VERSION_NUMBER 5 typedef struct EXT_API_VERSION { USHORT MajorVersion; USHORT MinorVersion; USHORT Revision; USHORT Reserved; } EXT_API_VERSION, *LPEXT_API_VERSION;
Where did you come up with 5, 5? I just debugged some of the other extensions that come with WINDBG to find those numbers. I also found that older WINDBGs use 3, 5. This is really a moot point though, we just need a basic frame work written, after which we can simply write all the commands we want! I'm not big on making things like this big issues or digging deep into their meaning when, it's really irrelevant to me as long as my extension loads.
The WinDbgExtensionDllInit API simply gives your application a virtual function table. This table *must* be named a certain name. Well, it doesn't have to be but it's easier when it is. The reason is that the windbgexts.h contains macros to make function calls to functions stored in this structure. If you don't use the same name, you'll have to create the calls yourself. This is my global in the code:
/*********************************************************** * Global Variable Needed For Functions ***********************************************************/ WINDBG_EXTENSION_APIS ExtensionApis = {0};
It's really no big deal, the macros just make it a little easier, that's all. You can do whatever you like though. This is the dump of WINDBGEXTS.H macros:
#define dprintf (ExtensionApis.lpOutputRoutine) #define GetExpression (ExtensionApis.lpGetExpressionRoutine) #define GetSymbol (ExtensionApis.lpGetSymbolRoutine) #define Disassm (ExtensionApis.lpDisasmRoutine) #define CheckControlC (ExtensionApis.lpCheckControlCRoutine) #define ReadMemory (ExtensionApis.lpReadProcessMemoryRoutine) #define WriteMemory (ExtensionApis.lpWriteProcessMemoryRoutine) #define GetContext (ExtensionApis.lpGetThreadContextRoutine) #define SetContext (ExtensionApis.lpSetThreadContextRoutine) #define Ioctl (ExtensionApis.lpIoctlRoutine) #define StackTrace (ExtensionApis.lpStackTraceRoutine)
What's next? Aside from these two APIs, you can have a CheckVersion() function to force your extension to only use commands on certain versions of WINDBG. I found this completely useless and didn't implement it as it's not required. So, let's just write a function!
The first function will be simple. We'll do "!help" which will dump the help.
/*********************************************************** * !help * * Purpose: WINDBG will call this API when the user types !help * * * Parameters: * N/A * * Return Values: * N/A * ***********************************************************/ DECLARE_API (help) { dprintf("Toby's Debug Extensions\n\n"); dprintf("!dumpstrings <ADDRESS register> - Dumps 20 strings in"\ "ANSI/UNICODE using this address as a pointer to strings (useful for" \ "dumping strings on the stack) \n"); /* String Split so it is readable in this article. */ }
dprintf(); is basically, "debug printf" and it's exactly like printf()! It will print output to the debugger. The DECLARE_API(<command>) is a simple way to declare the API name. Remember, the name you give the function is the name you use in the debugger. I called this help, so to use it, it's !help or !<dllname>.help. This is a simple function that will simply display a help message to the user.
The next thing we want to do is implement our string function. This function will take a parameter which will be a memory address. I also want it to work like current commands, if you do dc <address> then dc again, it will continue where it left off dumping the memory. So, let's see what I've come up with.
/*********************************************************** * !dumpstrings * * Purpose: WINDBG will call this API when the user types !dumpstrings * * * Parameters: * !dumpstrings or !dumpstrings <ADDRESS register> * * Return Values: * N/A * ***********************************************************/ DECLARE_API (dumpstrings) { static ULONG Address = 0; ULONG GetAddress, StringAddress, Index = 0, Bytes; WCHAR MyString[51] = {0}; GetAddress = GetExpression(args); if(GetAddress != 0) { Address = GetAddress; } dprintf("STACK ADDR STRING \n"); for(Index = 0; Index < 4*20; Index+=4) { memset(MyString, 0, sizeof(MyString)); Bytes = 0; ReadMemory(Address + Index, &StringAddress, sizeof(StringAddress), &Bytes); if(Bytes) { Bytes = 0; ReadMemory(StringAddress, MyString, sizeof(MyString) - 2, &Bytes); if(Bytes) { dprintf("%08x : %08x = (UNICODE) \"%ws\"\n", Address + Index, StringAddress, MyString); dprintf("%08x : %08x = (ANSI) \"%s\"\n", Address + Index, StringAddress, MyString); } else { dprintf("%08x : %08x = Address Not Valid\n", Address + Index, StringAddress); } } else { dprintf("%08x : Address Not Valid\n", Address + Index); } } Address += Index; }
So, the first function I use is GetExpression(). On the newer WINDBGs, it works like this. ADDRESS GetExpression(SYMBOLIC STRING). You pass in a symbolic string, such as the arguments to the command and it attempts to resolve it to an address. The arguments to the command are stored in args, so I pass in args. This will resolve symbols, addresses or even registers to numbers as would be with the case of passing in ESP.
I now have a static variable. If GetExpression() returned 0, it's possible there are no arguments. If this is the case, I use Address, my static variable. This works if someone does !dumpstrings. It will continue where it left off. At the end of the function, I always save Address as being the next location to dump.
The next function I use is dprintf() which works just like using printf. I've explained this one already, so onto the next tidbit. Addresses are 4 bytes long, so I will increment this address by 4 each time around in the loop. I want to dump 20 addresses, so I loop from 0 to 4*20. Very simple indeed.
Now, you cannot simply reference the memory returned as you are not in the process space of the application. So, WINDBG provides functions to do this such as ReadMemory(). (The windbgexts.h provides prototypes for all the APIs, so you can experiment if you cannot find the API online.) The ReadMemory() function takes 4 arguments:
ReadMemory(Address In Process To Read,
Local Variable to store the memory read,
size of the local variable,
pointer to a DWORD that returns the number
of bytes read from the memory location);
So, we pass in our pointer to the memory in the application, a pointer to place the data on return, then receive the number of bytes. If no bytes are returned, we print an invalid memory address; if the bytes are returned, we then reference this memory location to read up to 49 bytes (we have 51 and put two NULLs at the end for Unicode support). If I was able to read anything, I then attempt to display it using the dprintf() function in ANSI then in UNICODE. If the memory returns 0 bytes, I print an error specifying it's not a valid address. This is all very simple.
The next thing we need to do is create our .DEF file. This file will simply export our functions.
LIBRARY "TDBG.DLL" EXPORTS WinDbgExtensionDllInit ExtensionApiVersion dumpstrings help
Now, we need to build it. I like using make files and I use Visual Slickedit as my editor. I don't use the VC++ IDE at all. So, in the project, I've created a make file. This is how you would set it up. First, run VCVARS32.BAT which is located in the BIN directory of your VC++ installation. I moved mine to C:\ for easier use. The next thing to do is simply type "nmake" in the directory that the source code was extracted to.
C:\Programming\Programs\debugext\src\debug>\vcvars32
Setting environment for using Microsoft Visual C++ tools.
C:\Programming\Programs\debugext\src\debug>nmake
Microsoft (R) Program Maintenance Utility Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved.
cl /nologo /MD /W3 /Oxs /Zi /I ".\inc" /D "WIN32" /DLL /D "_WINDOWS"
/Fr.\obj\i386\\ /Fo.\obj\i386\\ /Fd.\obj\i386\\ /c .\tdbg.c
tdbg.c
C:\PROGRA~1\MICROS~2\VC98\INCLUDE\wdbgexts.h(526) : warning C4101: 'li' : unrefe
renced local variable
link.exe /DLL /nologo /def:tdbg.def /out:..\..\bin\tdbg.dll /pdb:tdbg.p
db /debug /debugtype:both USER32.LIB KERNEL32.LIB .\obj\i386\tdbg.obj
Creating library ..\..\bin\tdbg.lib and object ..\..\bin\tdbg.exp
rebase.exe -b 0x00100000 -x ..\..\bin -a ..\..\bin\tdbg.dll
REBASE: Total Size of mapping 0x00010000
REBASE: Range 0x00100000 -0x00110000
C:\Programming\Programs\debugext\src\debug>nmake clean
Microsoft (R) Program Maintenance Utility Version 6.00.8168.0
Copyright (C) Microsoft Corp 1988-1998. All rights reserved.
Deleted file - C:\Programming\Programs\debugext\src\debug\obj\i386\tdbg.obj
Deleted file - C:\Programming\Programs\debugext\src\debug\obj\i386\tdbg.sbr
Deleted file - C:\Programming\Programs\debugext\src\debug\obj\i386\vc60.pdb
C:\Programming\Programs\debugext\src\debug>
If you want to build again, you'd need to either change the source so the date is updated or type "nmake clean" to rebuild. You should now have a binary.
Volume in drive C has no label.
Volume Serial Number is 2CF8-F7B5
Directory of C:\Programming\Programs\debugext\bin
03/25/2004 08:56 PM <DIR> .
03/25/2004 08:56 PM <DIR> ..
03/25/2004 09:53 PM 2,412 tdbg.dbg
03/25/2004 09:53 PM 20,752 tdbg.dll
03/25/2004 09:53 PM 1,044 tdbg.exp
03/25/2004 09:53 PM 2,538 tdbg.lib
03/25/2004 09:53 PM 82,944 tdbg.pdb
5 File(s) 109,690 bytes
2 Dir(s) 12,229,009,408 bytes free
Simply copy this binary to a location that can be found by your WinDbg, such as your windows directory. You can specify the path to load these extensions using !load and !unload (to unload the extension if you built a new version/ want to force the debugger to unload it so you can reload the next one). You can use !<dll name>.<function name> or just !<function name>. The mentioning of the binary name will force WINDBG to look for it to load it. It can also help to distinguish between which DLL to use for a command if multiple DLL extensions export it.
Let's Try It Out
Now that we have created our debug extension, I move it to a location where WINDBG can load it (such as my windows directory). I then use "!tdbg.dumpstrings esp" to dump all the strings on the stack. This is the same application (restarted so addresses may change) restarted (I verified with dc esp, I get the same strings on the stack as before). I now want to dump all the pointers to strings on the stack. Let's try this and see what happens!
0:000> !tdbg.dumpstrings esp STACK ADDR STRING 0006febc : 77d43a09 = (UNICODE) "" 0006febc : 77d43a09 = (ANSI) "┬►" 0006fec0 : 77d43c7d = (UNICODE) "" 0006fec0 : 77d43c7d = (ANSI) "ïN♦ü∙☻☺" 0006fec4 : 0006fefc = (UNICODE) "" 0006fec4 : 0006fefc = (ANSI) "▐♥↔" 0006fec8 : 00000000 = Address Not Valid 0006fecc : 00000000 = Address Not Valid 0006fed0 : 00000000 = Address Not Valid 0006fed4 : 00000000 = Address Not Valid 0006fed8 : 0006ff1c = (UNICODE) "" 0006fed8 : 0006ff1c = (ANSI) "└ ♠" 0006fedc : 010028e4 = (UNICODE) "" 0006fedc : 010028e4 = (ANSI) "à└uöΦ├∩ 5î¢" 0006fee0 : 0006fefc = (UNICODE) "" 0006fee0 : 0006fefc = (ANSI) "▐♥↔" 0006fee4 : 00000000 = Address Not Valid 0006fee8 : 00000000 = Address Not Valid 0006feec : 00000000 = Address Not Valid 0006fef0 : 00000000 = Address Not Valid 0006fef4 : 77e7ad86 = (UNICODE) "" 0006fef4 : 77e7ad86 = (ANSI) "â|$♦" 0006fef8 : 00091ee8 = (UNICODE) "" 0006fef8 : 00091ee8 = (ANSI) "" 0006fefc : 001d03de = (UNICODE) "" 0006fefc : 001d03de = (ANSI) "" 0006ff00 : 00000118 = Address Not Valid 0006ff04 : 0000ffff = Address Not Valid 0006ff08 : bf8a75ed = Address Not Valid 0:000> !tdbg.dumpstrings STACK ADDR STRING 0006ff0c : 077d5cc8 = Address Not Valid 0006ff10 : 000001f3 = Address Not Valid 0006ff14 : 000001df = Address Not Valid 0006ff18 : 00000000 = Address Not Valid 0006ff1c : 0006ffc0 = (UNICODE) "" 0006ff1c : 0006ffc0 = (ANSI) "≡ ♠" 0006ff20 : 01006c54 = (UNICODE) "" 0006ff20 : 01006c54 = (ANSI) "ï≡ëuä9]ΣuV §▄↕" 0006ff24 : 01000000 = (UNICODE) "" 0006ff24 : 01000000 = (ANSI) "MZÉ" 0006ff28 : 00000000 = Address Not Valid 0006ff2c : 00091ee8 = (UNICODE) "" 0006ff2c : 00091ee8 = (ANSI) "" 0006ff30 : 0000000a = Address Not Valid 0006ff34 : 00000000 = Address Not Valid 0006ff38 : 00000000 = Address Not Valid 0006ff3c : 7ffdf000 = (UNICODE) "" 0006ff3c : 7ffdf000 = (ANSI) "" 0006ff40 : 80543940 = Address Not Valid 0006ff44 : f4910c5c = Address Not Valid 0006ff48 : 00000044 = Address Not Valid 0006ff4c : 00092b30 = (UNICODE) "" 0006ff4c : 00092b30 = (ANSI) "" 0006ff50 : 00092b50 = (UNICODE) "" 0006ff50 : 00092b50 = (ANSI) "WinSta0\Default" 0006ff54 : 00092b78 = (UNICODE) "" 0006ff54 : 00092b78 = (ANSI) "C:\WINDOWS.0\System32\notepad.exe" 0006ff58 : 00000000 = Address Not Valid 0:000> !tdbg.dumpstrings STACK ADDR STRING 0006ff5c : 00000000 = Address Not Valid 0006ff60 : 00000000 = Address Not Valid 0006ff64 : 00000000 = Address Not Valid 0006ff68 : 00000000 = Address Not Valid 0006ff6c : 00000000 = Address Not Valid 0006ff70 : 00000000 = Address Not Valid 0006ff74 : 00000000 = Address Not Valid 0006ff78 : 00000000 = Address Not Valid 0006ff7c : 00000000 = Address Not Valid 0006ff80 : ffffffff = Address Not Valid 0006ff84 : ffffffff = Address Not Valid 0006ff88 : ffffffff = Address Not Valid 0006ff8c : 00091ee8 = (UNICODE) "" 0006ff8c : 00091ee8 = (ANSI) "" 0006ff90 : 00000000 = Address Not Valid 0006ff94 : 00000001 = Address Not Valid 0006ff98 : 00272620 = (UNICODE) "" 0006ff98 : 00272620 = (ANSI) "(&'" 0006ff9c : 00272d00 = (UNICODE) "" 0006ff9c : 00272d00 = (ANSI) "░-'" 0006ffa0 : 00000000 = Address Not Valid 0006ffa4 : 00000000 = Address Not Valid 0006ffa8 : 0006ff34 = (UNICODE) "" 0006ffa8 : 0006ff34 = (ANSI) ""
We found two strings on the stack. Not bad and all I did was start Notepad. I didn't do anything else. This is a simple example of how this API could be used. Take a memory location and dump out all the pointers to strings. You are always
http://www.codeproject.com/KB/debug/debugtoolkit.aspx
Introduction
So you have written a great application and rolled it out to all your users and a week later you get the dreaded email:
Downloaded your program and I love it but it crashed. Can you please fix it.
You didn't expect details ... did you? So now you go through an extended exchange with the user trying to tie down the bug and eventually you give up and add it to the too hard basket in case someone else experiences the problem.
Of course you could ask the user to install Dr Watson or another tool which captures information when an application experiences an exception but what if the problem is a hang or if you would like internal application state information when the problem occured.
There is a better way. Using the debug toolkit outlined in this article you can get the users to send to you a file which provides:
- Trace Output
Trace output can be captured even in release builds. The trace output can be filtered to allow selective tracing output. Trace output is kept in a file which ensures it is not lost even when your program hangs or has an exception and its size is constrained so it won't fill up the users hard disk. - Command Line
The full command line used to run the program including any command line options. - Program Options
You need to do some work here but the you can save the current program options in effect. This can be done both in a description form and a more machine readable form. - Check Files
Check and dump details about any DLLs or files your program is dependent upon. You just need to tell it which files to check. - Check COM
Check and dump details about any COM objects your program is dependent upon. Once again just let it know which ones to check. - Printer
Dump the settings for the current default printer to help you diagnose printing related problems. - CPU
Dump details about the CPU in the PC. - Current Directory
Debug toolkit will dump the name of the current working directory. - Windows Version
Dump the version details of windows. - User definable
Of course if there are other things you would like to dump you can do that as well.
The trace log not only contains statements you have traced out. When an error occurs such as an exception, a hang or an assert fails the following details are dumped to the trace log so you can get a picture of what was happening:
- Call Stack
The current call stack so you can trace back the error to an exact line of code. - Register Dump
A dump of all processor registers and the 16 bytes of memory that the value in the register points to. - Task List
A list of active tasks on the PC. - Loaded modules
A list of DLLs and other modules loaded into your processes address space. - Windows Resources
Available windows GDI and USER resources. - Program Stack
A dump of the contents of the programs stack.
These are produced based on the following events:
- Asserts
When your code calls the debug toolkit to assert a condition that is false the toolkit will dump the programs state. - Exceptions
When windows traps an exception such as invalid memory access or divide by zero the toolkit will dump the programs state. - Automatically Detected Hangs
If the thread in which you create the debug toolkit object does not process any windows messages for a given period then the toolkit will dump the programs state and kill the program. - User Detected Hangs
If the user presses and holds <CTRL><F11><F12> for a given period then the toolkit will assume a user hang and dump the programs state and kill the program.
To get all of this you actually need do very little coding and can easily enhance or limit the functionality without the need to modify directly the provided code.
One of the design goals here was to make this class very easy to incorporate into an existing or new application. The main class is CDebugToolkit. It also has some helper classes but you should never need to access them. CDebugToolkit has been designed to form the base class for your own debug toolkit class in which you will override numerous methods and occasionally call methods on the base class. If you find yourself changing CDebugToolkit then either you have found a bug (in which case let us know) or you are providing a generic enhancement which could be used by others (in which case also let us know). In all other circumstances I would hope the functionality could live in the derived class.
By sticking with this approach you should be able to easily incorportate enhancements if/when they are made.
Class Overview
Public Methods
BOOL AddFile(const CString& sFile, const BOOL fTestDLL = FALSE, const BOOL fTestFile = FALSE);
Adds a file to the list of files that will be dumped should a log be required.
- sFile
Name of the file to add. If the file must live in a specific directory then include the full path. - fTestDLL
If true then the file will be treated as a DLL and the program will verify it can be loaded at startup. - fTestFile
If true then the file will be tested to see if it can be reached at startup.
Returns FALSE if the file was to be tested and the test failed.
BOOL AddCOM(const CString& sFile, const CLSID clsid, const BOOL fTest = FALSE);
Adds a COM objects to the list of COM objects that will be dumped should a log be required.
- sFile
Name of the COM object to add. - clsid
CLSID of the COM object to add. - fTest
If true the the COM object will be tested to see if it can be loaded at startup.
Returns FALSE if the COM object was to be tested and it failed.
virtual void Assert(DWORD dwLine, const CString& sFile, BOOL fCondition, const CString& sCondition, const CString& sDescription = "");
Evaluates fCondition. If it is FALSE then it writes trace output with details of the line, the condition and an optional description. See the DT_ASSERT* macros for an easy way to use this function. This is a virtual function so you can always replace it in your derived class.
- dwLine
The source line number containing the assert call. - sFile
The source file containing the assert call. - fCondition
The condition which is being evaluated. - sCondition
The condition in text format. - sDescription
A description of what was being tested.
virtual void Trace(const CString& sOut, const DWORD dwFilterFlag = 0xFFFFFFFF, const BOOL fForce = FALSE);
Writes out a message to the trace log. See the DT_TRACE* macros for an easy way to use this function. This is a virtual function so you can always replace it in your derived class.
- sOut
Text to trace out. - dwFilterFlag
A 32 bit field which will be and'd with the filter to see if this should be traced out. - fForce
If true then the text is written to the trace log regardless of any filters of the result of theOnTracemethod.
virtual BOOL DumpToFile(const CString& sFile, const CString& sPre = "");
virtual void DumpToFile(CFile& file, const CString& sPre = "");
virtual void DumpToFile(CFile& file, const CString& sPre = "");
Writes all debug information to a log file. These are virtual functions so you can always replace them in your derived class.
- sFile
Name of the log file to create. - file
An already open file to write the log contents into. - sPre
A text string to insert at the beginning of the file.
Returns FALSE if the file could not be created.
void Delete(void);
Deletes the cicular log file used to track trace output.
void Reset(void);
Calling this function causes the debug toolkit to reload the debug options. Use this function if you want to turn on/off debug capabilities at runtime.
void SetTraceFilter(const DWORD dwFilter)
This function sets the trace filter 32 bit flag which gets and'd with each trace request.
- dwFiler
32 bit flag filter.
void SetSuspendHang(const BOOL fSuspend)
This function suspends and re-activates the automated hang detection routine. Typically you would suspend this routine when performing particularly long tasks which do not allow the thread to process messages.
- fSuspend
TRUE to suspend. FALSE to re-activate.
static CDebugToolkit* GetDebugToolkit(void)
This function is a static function used to access the single instance of the debug toolkit. This function is used by the DT_ macros to allow you to easily write code to call instance methods.
Protected Methods
CDebugToolkit(void);
It is essential you call the base class constructor when constructing your derived class. You should also set any of the Debug Toolkit member variables that you don't like the default values for:
- m_sAppName
Name of your application. This is used as a filename so careful what you call it. - m_sVersion
Version of your application. - m_fExceptions
True if we are to trap exceptions - m_fAssert
True if we are to handle asserts - m_fHangDetect
True if we are to automatically detect main thread hangs - m_fUserHangDetect
True if we are to look for user nominated hangs - m_fTrace
True if we are to process trace output - m_fDumpOptions
True if we are to dump program options - m_fDumpFiles
True if we are to test and dump DLL and files. NOTE: For lots of files this can be slow. - m_fDumpLog
True if we are to dump the trace log - m_fDumpPrinter
True if we are to dump details about the default printer. NOTE: This is slow. - m_fDumpCPU
True if we are to dump details about the machines CPU - m_fDumpCWD
True if we are to dump current directory details - m_fDumpWindowsVersion
True if we are to dump windows version details - m_fDumpCommandLine
True if we are to dump command line details. - m_fDumpCOMInfo
True if we are to test and dump com object details. NOTE: For lots of com object this can be slow. - m_fDumpLoadableOptions
True if we are to dump machine loadable (but text) options NOTE: This would typically be the options in INI file format - m_iUserHangVk1
Key1 the user has to press when reporting a user detected hang - m_iUserHangVk2
Key2 the user has to press when reporting a user detected hang - m_dwUserHangWait
Maximum milliseconds the user has to press the keys for the user detected hang to be acknowledged. Smaller settings increase CPU load. - m_dwHangWait
Amount of time to wait before automatically detecting a hang - m_fPrefixMultilineTrace
True if we are to prefix 2nd & subsequent lines in a multiline trace output.
You would also typically add any files and COM objects that you want checking or dumping.
BOOL Initialise(void);
This method should be called from your derived classes constructor just before it returns. It is critical that you have set all options prior to calling this function.
Returns FALSE if an error occured in initialisation.
Macros
DT_TRACE[0..3](s, p1, p2, p3)
These macros are shortcuts for calling the Trace method. Using this macro prevents using filtering. This macro is used in an identical manner to MFCs TRACE[0..3] macros.
DT_TRACEF[0..3](f, s, p1, p2, p3)
These macros are identical to DT_TRACE[0..3] except the additional f parameter allows you to pass a 32 bit flag field which will be and'd with the trace filter flag and only traced if at least 1 set bit matches.
DT_ASSERT(b)
This macro is a shortcut for calling the Assert method. This macro is used in an identical manner to MFCs ASSERT macro.
DT_ASSERTS(b, s)
This macro is a shortcut for calling the Assert method. It extends the DT_ASSERT macro in that it allows you to describe what you are asserting.
Over-ridables
These methods have been specifically designed to be overridden in your class that derives from CDebugToolkit. Please do not change the methods directly.
virtual BOOL OnAssert(void);
Called to see if you want the assert handled. If you want the assert handled return TRUE.
virtual void OnEndAssert(void);
Called once the assert has been accepted and handled. Allows you to do some post assert processing such as maybe saving a log file and suggesting the user mail it to you.
virtual BOOL OnException(struct _EXCEPTION_POINTERS *pExceptionInfo);
Called to see if you want to handle the exception. If you want the exception handled return TRUE.
virtual void OnEndException(void);
Called once the exception has been accepted and handled. You need to be very careful what you do here as exceptions usually mean something is quite wrong.
virtual BOOL OnHang(void);
Called to see if you want to handle the hang automatically detected. If you want the hang handled return TRUE. You may want to ask the user if the program appears to have hung ... just in case.
virtual void OnEndHang(void);
Called once the hang has been accepted and handled. Allows you to do some final processing before the task is killed. BEWARE when this method is called you are not running in the main thread.
virtual BOOL OnUserHang(void);
Called to see if you want to handle the user reported hang. If you want the hang handled return TRUE.
virtual void OnEndUserHang(void);
Called once the user reported hang has been accepted and handled. Allows you to do some final processing before the task is killed. BEWARE when this method is called you are not running in the main thread.
virtual BOOL OnTrace(const DWORD dwFilterFlag);
Called to see if you want the trace processed. If you want the trace handled return TRUE.
virtual void OnReset(void);
Called to allow you to change debug toolkit processing options before debugging is restarted.
virtual BOOL OnDelete(void);
Called to allow you to do some processing before the delete request is handled. Return FALSE to cancel the delete.
virtual void OnAbnormalExit(void);
Called to allow you to do some processing when we detect the program did not exit normally last time it was run. This function is usually run prior to completion of the application constructor which limits some of the MFC functionality you can use.
virtual void OnTestDLLFail(const CString& sFile, DWORD dwError);
Called when a DLL could not be loaded. Override this if you want to tell the user that a critical DLL is missing.
virtual void OnTestCOMFail(const CLSID clsid, const CString& sFile, DWORD dwError);
Called when a COM file could not be loaded. Override this if you want to tell the user that a critical COM object is missing.
virtual void OnTestFileFail(const CString& sFile, DWORD dwError);
Called when a file could not be found. Override this if you want to tell the user that a critical file is missing.
virtual CString GetDumpOtherAtEvent(void);
Allows you to provide additional data to write to the trace log when a hang, exception or assert event occurs.
virtual CString GetDumpOtherAtSave(void);
Allows you to provide additional data to write to the log file when it is being written to disk.
virtual void GetOptionDescriptions(CString& sOptionDescriptions);
Allows you to dump in human readable form the options currently in effect.
virtual void ExportOptions(CFile& file);
Allows you to dump in machine readable (but text) form to options currently in effect.
Adding Debug Toolkit To Your Project
- Add these files to your project.
- DebugToolkit.cpp // main debug toolkit class - Do Not Change
- DebugToolkit.h // main debug toolkit class - Do Not Change
- FastLogger.cpp - Do Not Change
- FastLogger.h - Do Not Change
- FileInfo.cpp - Do Not Change
- FileInfo.h - Do Not Change
- K32exp.cpp - Do Not Change
- K32exp.h - Do Not Change
- MyDebugToolkit.cpp // shell for your overriden debug toolkit
- MyDebugToolkit.h // shell for your overriden debug toolkit
- ProcessorInfo.h - Do Not Change
- ProgressWnd.cpp - Do Not Change
- ProgressWnd.h - Do Not Change
- WindowsVersion.h - Do Not Change
- Add
VERSION.LIBto your link options. - Select the
Generate mapfileoption in your projects link options. - In C++ Listing Files options select
Assembly with Source Code. - Edit your
CApplicationderived class to add a new public member variable
CMyDebugToolkit m_mdt; - Add
#include "MyDebugToolkit.h"to the top of youCApplicationderived class header file. You may need to do the same in other c++ files which will call the debug toolkit. - Customise your new
CMyDebugToolkitclass. - Now replace all
ASSERT()andTRACE[0..3]calls with theDT_equivaluent. - When you produce your final release build don't forget to create a zip file containing your projects executables along with the .MAP and all the .ASM files. You will need these to decode the call stack to the original source code line.
CAUTION: When running your code under the debugger this class will not trap exception events. This is because the debugger takes priority and traps it first.
There is obviously a lot of functionality here but there are also lots of possible enhancements. Should you make any or have any suggestions please let me know.
Possible Enhancements
Here are some possible enhancements I have thought of but either don't have the time or equipment to do.
- Fix issue with the program not correctly dumping COM object registration details on NT.
- Add task listing for NT.
Fixes
- 28 March 2000 : Fixed toolhelp APIs so they do not cause runtime errors on Windows NT.
NOTE: I do post fixes to the code project site but sometimes it can take a few days for them to get there. The latest source is always available here.
License
http://www.codeproject.com/KB/debug/moomoo.aspx
Introduction
Our software just published version 1.0 . My clients often encountered exceptions and blue screens that caused the program to terminate. Most of time, we can not reproduce the bug in our systems. So catching and analyzing a memory dump file is important. This article teaches you how to use hands-on tools to catch and read memory dump files.
The hands-on tools are :-
- Dr. Watson, which is included by almost all the windows systems.
- VC6 and VC7 compiler, we need to recompile the project to generate Map files and COM files for tracing the bug.
- Dumpchk can check dump file, before you are going to send it.
- Windbg and kd can give you detailed information about the spot when exception occurred. Symbol files are always necessary for debugging dump files.
Details
Before reproducing the bug, Dr. Watson must be installed and running for srwtsn32.log to be created. We also need to configure the project to generate debug information, MAPFILE and COD file, then recompile the project.
Configuring Dr. Watson
To start Dr. Watson and set it as default debugger, go to command line, type:
drwtsn32 –I
If you want to change the settings you can run drwtsn32 at command line any time. Default settings is ok. Then if a program crashes, Dr. Watson will write drwtsn32.log and dump memory into user.dmp.
Reconfiguring Project Settings in Visual C++ environment
You can prepare the source code to be ready for Dr. Watson and memory dump. You need to take three simple step as following:
- You need to Generate debug symbols for the Win32 Release version of your .EXE and/ or .DLL files in settings configuration for the project, for the linker, Open "Project Settings" window( click Settings item in Project menu), select Link tab, check Generate Mapfile, and check Generate debug info. For the compiler, check Debug Info: Program Database, under General in C/C++ tab.
- Creating and keeping a copy of your application's .MAP and .COD intermediate files for further reference. Under Link tab, in Project Options editbox, add switch "/mapinfo:exports" ( switch /mapinfo:exports causes the linker to describe the binary's exported functions.) Add switch "/mapinfo:lines" (The switch /mapinfo:lines will add line numbers in mapfile). A .COD file contains a mixture of source code, assembly code and memory addresses, and it will help you to locate the offending line of code within a function that has been identified via the .MAP file. Under General section in C/C++ tab, add switch "/FAcs" in project options.
- Specifying a fixed load base hexadecimal address for your .EXE and/or .DLL files. Under Output in the Link tab, input a hexadecimal address in Base address, such as 0x602f0000
Examples
Use the Visual C++ application wizard to create MFC win32 exe file names "MapDemo". Add following lines in InitInstance function in CMapDemoApp class.
char * pch1 = 0; char * pch2 = 0; pch1 = (char *)malloc( 20 ); pch2 = (char *)malloc( 20 ); pch1 = pch2; free( pch1 ); free( pch2 );
This code will eventually fail because the application will try to release the same block of memory twice. Run the program and it will crash. Open the file drwtsn32.log:
*----> Module List <----*
(0000000000400000 - 000000000040b000:
C:\WORK\CODEPROJ\MapDemo\Debug\MapDemo.exe
(0000000010200000 - 000000001026c000: C:\WINDOWS\System32\MSVCRTD.dll
(000000005f400000 - 000000005f4e5000: C:\WINDOWS\System32\MFC42D.DLL
(00000000605d0000 - 00000000605d8000: C:\WINDOWS\System32\mslbui.dll
(0000000070a70000 - 0000000070ad4000: C:\WINDOWS\system32\SHLWAPI.dll
(0000000074720000 - 0000000074764000: C:\WINDOWS\System32\MSCTF.dll
(0000000077120000 - 00000000771ab000: C:\WINDOWS\system32\OLEAUT32.DLL
(00000000771b0000 - 00000000772d1000: C:\WINDOWS\system32\OLE32.DLL
(0000000077c00000 - 0000000077c07000: C:\WINDOWS\system32\VERSION.dll
(0000000077c10000 - 0000000077c63000: C:\WINDOWS\system32\msvcrt.dll
(0000000077c70000 - 0000000077cb0000: C:\WINDOWS\system32\GDI32.dll
(0000000077d40000 - 0000000077dc6000: C:\WINDOWS\system32\USER32.dll
(0000000077dd0000 - 0000000077e5d000: C:\WINDOWS\system32\ADVAPI32.dll
(0000000077e60000 - 0000000077f46000: C:\WINDOWS\system32\kernel32.dll
(0000000077f50000 - 0000000077ff7000: C:\WINDOWS\System32\ntdll.dll
(0000000078000000 - 000000007807f000: C:\WINDOWS\system32\RPCRT4.dll
*----> State Dump for Thread Id 0x3274 <----* eax=00000001 ebx=7ffdf000 ecx=10261558 edx=00010000 esi=0012febc edi=7ffdf000 eip=10213638 esp=0012fe5c ebp=0012fe6c iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
function: MSVCRTD!_free_dbg_lk
10213616 7521 jnz MSVCRTD!_free_dbg_lk+0xc9
(10213639)
10213618 6814732510 push 0x10257314
1021361d 6a00 push 0x0
1021361f 6814040000 push 0x414
10213624 68f4712510 push 0x102571f4
10213629 6a02 push 0x2
1021362b e810180000 call MSVCRTD!_CrtDbgReport (10214e40)
10213630 83c414 add esp,0x14
10213633 83f801 cmp eax,0x1
10213636 7501 jnz MSVCRTD!_free_dbg_lk+0xc9
(10213639)
FAULT ->10213638 cc int 3
10213639 33c0 xor eax,eax
1021363b 85c0 test eax,eax
1021363d 75c9 jnz MSVCRTD!_free_dbg_lk+0x98
(10213608)
1021363f 8b4d08 mov ecx,[ebp+0x8]
10213642 83e920 sub ecx,0x20
10213645 894dfc mov [ebp-0x4],ecx
10213648 8b55fc mov edx,[ebp-0x4]
1021364b 8b4214 mov eax,[edx+0x14]
1021364e 25ffff0000 and eax,0xffff
10213653 83f804 cmp eax,0x4
*----> Stack Back Trace <----*
*** WARNING: Unable to verify checksum for
C:\WORK\CODEPROJ\MapDemo\Debug\MapDemo.exe
*** ERROR: Symbol file could not be found. Defaulted to
export symbols for C:\WINDOWS\system32\kernel32.dll -
WARNING: Stack unwind information not available. Following
frames may be wrong.
ChildEBP RetAddr Args to Child
0012fe6c 10213541 00324708 00000001 7ffdf000 MSVCRTD!_free_dbg_lk+0xc8
0012fea0 102134ce 00324708 00000001 0012fee8 MSVCRTD!_free_dbg+0x41
0012feb0 00401360 00324708 7ffde000 00324238 MSVCRTD!free+0xe
0012fee8 5f4335c3 7ffdf000 7ffde000 7ffdf000
MapDemo!CMapDemoApp__InitInstance+0x132
0012ff08 00402248 00400000 00000000 001423ac MFC42D!AfxWinMain+0x83
0012ff20 00402153 00400000 00000000 001423ac MapDemo!WinMain+0x18
0012ffc0 77e814c7 7ffdf000 7ffde000 7ffdf000
MapDemo!WinMainCRTStartup+0x1b3
0012fff0 00000000 00401fa0 00000000 78746341
kernel32!GetCurrentDirectoryW+0x44
Because the code was fault in the memory free function "_free_dbg_lk", it is called by function Initinstance(), so we search the stack-track-back part, on the fourth line, it indicates that the sub function was called by InitInstance function at the offset 0x132.
So, look at the .COD intermediate file, "MapDemo.COD", search for the module "InitInstance()", the offset address at the beginning of the module is 0x00ae, add the address of (0x132+0xae = 0x01e0 ) , so we know the program was crashed at line 79 in source code file "MapDemo.cpp".
Another Example - How to trace fault into DLL file( in – process, and out – process DLL file).
Use Application Wizard in Visual Studio to generate a DLL library project MapDemoDll, create a export function DllSquareRoot(), and put a fault code in that function :-
char* pChar = NULL; *pChar = 'a'; // This is fault line 70
The program will throw an exception that trying to access to a invalid memory address(0x0000). Use Application Wizard to generate an client program calling the DllSquareRoot function in MapDemoDll.dll. Before running the exe program with the DLL library, we need to set the MapDemoDll’s project’s settings. Generate MAP, COD file, specify the base address for loading at 0x60f20000. Compile the project and run the program.
Program crashed and screen displayed the message like that "DemoDllClient.exe has generated errors and will be closed by windows. You will need to restart the program." Following is the drwsnt32.log generated by Dr. Watson.
State Dump for Thread Id 0x7b0 eax=00000000 ebx=005e4b68 ecx=00135dd0 edx=60f35918 esi=0012f27c edi=0012f26c eip=60f21320 esp=0012f204 ebp=0012f26c iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
function: DllSquareRoot
60f212f9 8d7da4 lea edi,[ebp+0xa4]
ss:00c8c83e=????????
60f212fc b917000000 mov ecx,0x17
60f21301 b8cccccccc mov eax,0xcccccccc
60f21306 f3ab rep stosd
es:0012f26c=0012f2cc
60f21308 e8f3020000 call AfxGetStaticModuleState
(60f21600)
60f2130d 50 push eax
60f2130e 8d4df8 lea ecx,[ebp+0xf8]
ss:00c8c83e=????????
60f21311 e8a4010000 call
AFX_MAINTAIN_STATE2::AFX_MAINTAIN_STATE2 (60f214ba)
60f21316 c745f400000000 mov dword ptr [ebp+0xf4],0x0
ss:00c8c83e=????????
60f2131d 8b45f4 mov eax,[ebp+0xf4]
ss:00c8c83e=????????
FAULT ->60f21320 c60078 mov byte ptr [eax],0x78
ds:00000000=??
60f21323 dd4508 fld qword ptr [ebp+0x8]
ss:00c8c83e=????????????????
60f21326 dc1d4841f360
ds:60f34148=0000000000000000
fcomp qword ptr [_real (60f34148)]
60f2132c dfe0 fstsw
60f2132e f6c401 test ah,0x1
60f21331 7520 jnz _enc$textbss$begin+0x5f43
(60f29e53)
60f21333 8b4d0c mov ecx,[ebp+0xc]
ss:00c8c83e=????????
60f21336 51 push ecx
60f21337 8b5508 mov edx,[ebp+0x8]
ss:00c8c83e=????????
60f2133a 52 push edx
60f2133b e848060000 call sqrt (60f21988)
60f21340 83c408 add esp,0x8
The program crashed at address 0x60f21230, here is part of the mapfile
Address Publics by Value Rva+Base Lib:Object
0001:00000050 ?_GetBaseMessageMap@CMapDemoDllApp@@KGPBUAFX_MSGMAP@@XZ
60f21050 f MapDemoDll.obj
0001:00000080 ?GetMessageMap@CMapDemoDllApp@@MBEPBUAFX_MSGMAP@@XZ
60f21080 f MapDemoDll.obj
0001:000000c0 ??0CMapDemoDllApp@@QAE@XZ 60f210c0 f MapDemoDll.obj
0001:00000120 ??_GCMapDemoDllApp@@UAEPAXI@Z 60f21120 f i MapDemoDll.obj
0001:00000120 ??_ECMapDemoDllApp@@UAEPAXI@Z 60f21120 f i MapDemoDll.obj
0001:00000190 ??1CMapDemoDllApp@@UAE@XZ 60f21190 f i MapDemoDll.obj
0001:000002f0 _DllSquareRoot 60f212f0 f MapDemoDll.obj
0001:000003b2 ?WinHelpA@CWinApp@@UAEXKI@Z 60f213b2
f mfc42d:MFC42D.DLL
0001:000003b8 ?OnDDECommand@CWinApp@@UAEHPAD@Z 60f213b8
f mfc42d:MFC42D.DLL
0001:000003be ?DoWaitCursor@CWinApp@@UAEXH@Z 60f213be
f mfc42d:MFC42D.DLL
0001:000003c4 ?DoMessageBox@CWinApp@@UAEHPBDII@Z 60f213c4
f mfc42d:MFC42D.DLL
0001:000003ca ?SaveAllModified@CWinApp@@UAEHXZ 60f213ca
f mfc42d:MFC42D.DLL
0001:000003d0 ?InitApplication@CWinApp@@UAEHXZ 60f213d0
f mfc42d:MFC42D.DLL
The biggest function address that just less than the crashing address 0x60f21320 is 0x60f212f0, which belongs to DllSquareRoot function in MapDemoDll object. We also can follow that calculation: crash_address - preferred_load_address - 0x1000. Why do we need to subtract 0x1000, because the first part the binary is the Portable Executable (PE), which is 0x1000 bytes long. So the calculation is: 0x60f21320 – 0x60f20000 – 0x1000 = 0x320
Line numbers for .\Debug\MapDemoDll.obj
(D:\work\MapDemoDll\MapDemoDll.cpp) segment .text
44 0001:00000050 44 0001:00000080
55 0001:000000c0 58 0001:000000f0
63 0001:00000220 66 0001:000002f0
67 0001:00000308 69 0001:00000316
70 0001:0000031d 72 0001:00000323
73 0001:00000333 74 0001:00000353
75 0001:00000361 76 0001:0000037a
Above is the information section of MAPFILE, like that: 44 0001:00000050, first number is line number, the second number is the offset from the beginning of the code section in which this line occurred. So we will know the program was crashed at line 70 in MapDemoDll.cpp. Which tried to write a number into an invalid address.
How to read and debug by memory dump file
- Get the symbol table or symbol server address. To prepare for debugging the dump file, you should get the Microsoft symbol server address or download the symbols from Microsoft (http://www.microsoft.com/whdc/ddk/debugging/symbolpkg.mspx#Windows%20symbol%20packages)
- Download the debugging tools for Windows. Those debugging tools can read the windows memory dump and simulate the original memory environment.
- Need to install the support tools from original installation CD. Before using memory dump to debug, we need to check if it is not corrupted by dumpchk.exe, which is in the windows installation CD. Go to command line and input: Dumpchk -a [dump file]
- If the memory dump file passes the check, now we can use windbg or kd to debug the memory dump. After the installation is complete, start a command prompt, change to the path where the debugging tools were installed. Use the one of following command to load the dump file into debugger. Windbg.exe is a windows-based debugger, kd.exe is a command line debugger.
windbg -y SymbolPath -i ImagePath -z DumpFilePath kd -y SymbolPath -i ImagePath -z DumpFilePath
The SymbolPath is the path of symbols, it is either a local path where the symbol files were downloaded, or the server path which is an URL. ImagePath is the path of windows image files, which are contained in the /I386 folder on the WINDOWS. Installation CD-ROM. DumpPath is the path and file name for the dump that you are examining.
Example
kd -y SRV*f:\symbols*http://msdl.microsoft.com/download/symbols -i e:\i386 -z f:\dumps\minidump.dmp kd -y f:\symbols -i e:\i386 -z "f:\my debug files\minidump.dmp" windbg -y f:\symbols -i e:\i386 -z "f:\dumps\minidump.dmp"
Both of the debuggers accept commands which gather information from dump file, such as:
The !analyze -show command displays the Stop error code
(also known as the bug check code) and its parameters.
The !analyze -v command displays verbose output.
The !drivers command displays a list of the drivers that
were loaded on the computer when the problem occurred.
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
http://www.codeproject.com/KB/debug/automemorydump.aspx
Introduction
We can make a debugger to pop up whenever an application crashes. This debugger is called ‘Postmortem’ Debugger or JIT Debugger.
Various postmortem debuggers are
1) WinDbg
2) VS 6.0/NET
3) Dr Watson
We can make these post mortem debuggers to automatically generate a crash memory dump too.
Here we describe about WinDbg debugger,
- How to configure it as a postmortem debugger,
- How to automatically generate a memory dump file,
- How to run through the memory stack of the application when it crashed.
How to configure WinDbg as a Postmortem debugger:
1) Download the WinDbg from the Microsoft debugger site.
http://www.microsoft.com/whdc/ddk/debugging/installx86.mspx
2) Install it to an appropriate directory
3) To change the postmortem debugger to WinDbg, run WinDbg -I. (The I must be capitalized.) This command will display a success or failure message after it is used. When WinDbg is the postmortem debugger, it will be activated whenever an application crashes.
(If you have not reconfigured Windows' postmortem settings, Dr. Watson is used as the default postmortem debugger. This setting can be changed programmatically or through the registry; any changes take effect immediately. )
How to automatically generate a memory dump file:
We need to modify the registry for this.
Editing the Registry
On an x86 computer, the postmortem settings are stored in the \\HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AeDebug key.
There are two registry values that should appear in these keys:
Debugger
This REG_SZ value specifies the debugger that will handle postmortem debugging. The full path to the debugger must be listed, unless the debugger is located in a directory that is in the default path.
Auto
This REG_SZ value is always either 0 or 1. If Auto is set to 0, a message box will be displayed prior to postmortem debugging.
When an unhandled application error occurs, Windows checks to see if the Debugger and Auto registry values exist.
If the Auto value is 0, a message box will appear.
In Windows NT and Windows 2000, the message box will have one of the following formats:
- If the Debugger value contains the name of a valid debugger or Dr. Watson, the message box will have two buttons: OK and Cancel. If the OK button is pressed, the application will be terminated. If the Cancel button is pressed, the tool specified in the Debugger value will be started.
- If the Debugger value is empty, the message box will have only an OK button and no debugger will start.
Registry Examples
Prior to change the registry entry will be
Debugger = " Path\windbg.exe" -p %ld -e %ld -g
For auto generation of dump file modify the registry for Debugger key as shown
Debugger = “Path\WinDbg.exe" -p %ld –c “.dump /ma /u C:\CrashDump.dmp” -e %ld –g
NOTE :
1) Be careful with the registry changes. Execute the test.exe to verify that the registry changes are correct. When you run test.exe the application will automatically bring up the debugger and generate a crashdump.dmp file in C:\ drive
2) -c option should immediately come after -p %ld
Info : ( taken from “Using Debugging tools for windows” help file )
-c "command"
Specifies the initial debugger command to run at start-up. This command must be enclosed in quotation marks. Multiple commands can be separated with semicolons. (If you have a long command list, it may be easier to put them in a script and then use the -c option with the $< (Run Script File) command.)
You can change the parameters of the command as per your requirements.
Parameters
Options
Can be any number of the following:
/o
Allows the debugger to overwrite a previous dump file with the same name. If this is not included and the file name is in use, the dump file will not be written.
/f
(Kernel mode:) Causes the debugger to create a Complete Memory dump.
(User mode:) Causes the debugger to create a full user dump. Note that, despite their names, the largest minidump file actually contains more information than a full user-mode dump! For example, . dump /mf or . dump /ma will create a larger and more complete file than . dump /f.
/m[MiniOptions]
Causes the debugger to create a Small Memory dump (in kernel mode) or a minidump (in user mode). If neither /f nor /m is specified, /m is the default.
In user mode, /m can be followed with additional MiniOptions specifying the extra data that should be included in the dump. If no MiniOptions are included, the dump will include module, thread, and stack information, but no additional data. Any of the following MiniOptions can be added to change the contents of the dump file; they are case-sensitive.
| MiniOption | Effect |
| a | Creates a minidump with all optional additions. The /ma option is equivalent to /mfhuFt — it adds full memory data, handle data, unloaded module information, basic memory information, and thread time information to the minidump. |
| f | Adds full memory data to the minidump. All accessible committed pages owned by the target application will be included. |
| F | Adds all basic memory information to the minidump. This adds a stream to the minidump that contains all basic memory information, not just information on valid memory. This allows the debugger to reconstruct the complete virtual memory layout of the process when the minidump is being debugged. |
| h | Adds data about the handles associated with the target application to the minidump. |
| u | Adds unloaded module information to the minidump. This is only available in Windows Server 2003. |
| t | Adds additional thread information to the minidump. This includes thread times, which can be displayed by using .ttime (Display Thread Times) when debugging the minidump. |
| i | Adds secondary memory to the minidump. Secondary memory is any memory referenced by a pointer on the stack or backing store, plus a small region surrounding this address. |
| p | Adds process environment block (PEB) and thread environment block (TEB) data to the minidump. This can be useful if you need access to Windows system information regarding the application's processes and threads. |
| w | Adds all committed read-write private pages to the minidump. |
| d | Adds all read-write data segments within the executable image to the minidump. |
| r | Deletes from the minidump those portions of the stack and store memory that are not useful for recreating the stack trace. Local variables and other data type values are deleted as well. This option does not make the minidump smaller (because these memory sections are simply zeroed), but it is useful if you want to protect the privacy of other applications. |
| R | Deletes the full module paths from the minidump. Only the module names will be included. This is a useful option if you want to protect the privacy of the user's directory structure. |
These MiniOptions can only be used when creating a user-mode minidump. They should follow the /m specifier.
/u
Causes dump file names to have the date, time, and PID appended to them. This ensures that dump file names will be unique.
/a
Causes dump s to be generated for all currently-debugged processes. If /a is used, the /u option should be included as well to ensure that each file has a unique name.
/b[a]
Causes a .cab file to be created. If this option is included, FileName is taken to be the CAB file name, not the dump file name. A temporary dump file will be created, this file will be packaged into a CAB, and then the dump file deleted. If the b option is followed by a, all symbol and image files will be packaged into the CAB as well.
/c "Comment"
Specifies a comment string that will be written to the dump file. If Comment contains spaces, it must be enclosed in double quotes. When the dump file is loaded, the Comment string will be displayed.
/kpmf File
(Only when creating a kernel-mode Complete Memory dump) Specifies a file that contains physical memory page data.
FileName
Specifies the name of the dump file. You can specify a full path and file name or just the file name. If the file name contains spaces, FileName should be enclosed in quotation marks. If no path is specified, the current directory is used.
How to run through the memory stack of the application when it crashed:
Use command ‘KV’ for displaying the stack.
If the stack is corrupted, then u can manually walk though the stack as shown
Steps:
1) lm - loads all the modules, keep note of address range of all modules
2) dd esp – dumps the stack. stack has the return addresses of all the functions in the stack.
3) Guess the module, which would have caused the crash and note its address range.
4) Look at the stack dump, and find an address, which is there in the suspected address range.
5) ln ‘address’ - this provides info about the function near that address.
6) u ‘address’ - If the function looks suspicious, you can un-assemble the assembly code near that address range.
7) If you did not find any useful info about the suspected function,
repeat steps 4 – 7,
8) If you did not find any useful info about the suspected module,
repeat steps 3 - 8
Useful commands in Windbg: ( taken from “Using Debugging tools for windows” help file )
1) LN (List Nearest Symbols)
The LN command displays the symbols at or near the given address.
Syntax
Ln <U>address</U>
Parameters
address
The address where to begin searching for symbols. The nearest symbols, either before or after address, are displayed
2) .dump (Create dump File)
The. dump command creates a user-mode or kernel-mode crash .dump file.
Syntax
.dump <U>Options</U> <U>FileName</U>
3) D, DA, DB, DC, Dd, DD, DF, DP, DQ, DU, DW, DYb, DYd (Display Memory)
The D* commands display the contents of memory in the given range.
Syntax
d{a|b|c|d|D|f|p|q|u|w} [<U>range</U>]
dy{b|d} [<U>range</U>]
d [<U>range</U>]
Parameters
range
The memory area to display. For more syntax details, see Address and Address Range Syntax. If you omit range, the command will display memory starting at the ending location of the last display command. If range is omitted and no previous display command has been used, the display begins at the current instruction pointer.
4) U (Unassemble)
The U command displays an assembly translation of the specified program code in memory.
This command should not be confused with the ~U (Unfreeze Thread) command.
Syntax
u [<U>Range</U>]
Parameters
Range
The memory area that contains the instructions to unassemble. If Range is not specified, the disassembly begins at the current address. If no range size is specified, the default size is eight instructions on an x86 processor and nine instructions on an Itanium processor.
5) LM (List Loaded Modules)
The LM command lists the specified loaded modules. The output includes the status and the path to the module.
Syntax
lm <U>Options</U> [<U>a</U> <U>Address</U>] [<U>m</U><U>Pattern</U>]
6) K, KB, KD, KP, Kp, KV (Display Stack Backtrace)
The K* commands display the stack frame of the given thread, along with related information..
Reference:
“Using Debugging tools for windows” help file
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
http://www.codeproject.com/KB/debug/windbg_part1.aspx
Table of contents
- Introduction
- Debugging Scenarios
- WinDbg Features
- WinDbg Settings
- Commands
- Example
- Epilogue
- References
Introduction
In my professional career, I have seen most of us use Visual Studio for debugging but not many of the other debuggers that come for free. You may want such a debugger for many reasons, for example, on your home PC which you do not use for development but on which a certain program crashes from time to time. From the stack dump, you can figure out if IE crashed because of a third party plug-in.
I did not find any good quick starters for WinDbg. This article discusses WinDbg with examples. I assume you know the basic concepts of debugging – stepping in, stepping out, breakpoints and what it means to do remote debugging.
Note that this is meant to be a Getting Started document, which you can read and start using WinDbg. To know more about specific commands, consult the WinDbg documentation. You can use the commands presented in this document with any debugger provided by Microsoft, e.g. from the Command window of Visual Studio .NET.
This article is based on WinDbg 6.3.
This is the first of a series of articles on debugging. In my next article, I shall explain how to write debugger extension DLLs.
Overview of Debuggers
A brief overview of the Windows debuggers that you can download for free from here:
- KD – Kernel debugger. You want to use this to remote debug OS problems like blue screens. You want it if you develop device drivers.
- CDB – Command-line debugger. This is a console application.
- NTSD – NT debugger. This is a user-mode debugger that you can use to debug your user-mode applications. Effectively, this is Windows-style UI added to CDB.
- Windbg – wraps KD and NTSD with a decent UI. WinDbg can function both as a kernel-mode and user-mode debugger.
- Visual Studio, Visual Studio .NET – use the same debugging engine as KD and NTSD and offer richer UI than WinDbg for debugging purposes.
Comparison of Debuggers
| Feature | KD | NTSD | WinDbg | Visual Studio .NET |
| Kernel-mode debugging | Y | N | Y | N |
| User-mode debugging | Y | Y | Y | |
| Unmanaged debugging | Y | Y | Y | Y |
| Managed debugging | Y | Y | Y | |
| Remote debugging | Y | Y | Y | Y |
| Attach to process | Y | Y | Y | Y |
| Detach from process in Win2K and XP | Y | Y | Y | Y |
| SQL debugging | N | N | N | Y |
WinDbg
WinDbg is a debugger that wraps NTSD and KD with a better UI. It provides command-line options like starting minimized (-m), attach to a process by pid (-p) and auto-open crash files (-z). It supports three types of commands:
- regular commands (e.g.: k). The regular commands are to debug processes.
- dot commands (e.g.: .sympath). The dot commands are to control the debugger.
- extension commands (e.g.: !handle) – these are custom commands that you can add to WinDbg; they are implemented as exported functions in extension DLLs.
PDB files
PDB files are program database files generated by the linker. Private PDB files contain information about private and public symbols, source lines, types, locals and globals. Public PDB files do not contain types, local and source line information.
Debugging Scenarios
Remote Debugging
Doing remote debugging using WinDbg is easy and can be done in one of a number of ways. In the following, ‘debugging server’ is the debugger running on the machine where you’d like to debug; ‘debugging client’ is the debugger controlling the session.
- Using the debugger: You need CDB, NTSD or WinDbg on the server. A WinDbg client can connect to any of CDB, NTSD and WinDbg, and vice versa. The server and client have choices of TCP and named pipes for communication protocol.
- To start a server:
- WinDbg –server npipe:pipe=pipename (note: multiple clients can connect), or
- from within WinDbg: .server npipe:pipe=pipename (note: single client can connect)
You can start multiple server sessions using multiple protocols. You can password-protect a session.
- To connect from a client:
- WinDbg -remote npipe:server=Server, pipe=PipeName[,password=Password]
- from within WinDbg: File->Connect to Remote Session: for connection string, enter npipe:server=Server, pipe=PipeName [,password=Password]
- To start a server:
- Using remote.exe: remote.exe uses named pipes for communicating. If you use a console-based application like KD, CDB or NTSD, you could use remote.exe to do remote debugging. Note: use @q (not q) to quit the client without quitting the server.
- To start a server:
- Remote.exe /s “cdb –p <pid>” test1
- To connect from a client:
- Remote.exe /c <machinename> test1
test1 above is the arbitrary named pipe name we chose.
- To start a server:
Server will display who all are connected from which servers and commands executed. You can quit the server by issuing ‘qq’; or quit the client using File->Exit. You’d need to belong to the “Debugger Users” user group and the server has to allow remote connectivity if you want to remote-debug.
Just-in-time Debugging
The section “Enabling Postmortem Debugging” in the WinDbg documentation discusses this well. In short, you can set WinDbg as the default JIT debugger by running Windbg –I. This sets the registry key HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug to WinDbg. To set WinDbg as the default managed debugger, you’d need to set these registry keys explicitly:
HKLM\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSettingto 2HKLM\Software\Microsoft\.NETFramework\DbgManagedDebuggerto Windbg.
With the JIT setting, WinDbg will be launched if an application throws an exception while not being debugged and does not handle the exception itself.
64-bit Debugging
All these debuggers support 64-bit debugging on AMD64 and IA64.
Managed Debugging
WinDbg 6.3+ supports managed debugging, with the Whidbey .NET CLR. There is a good discussion on managed debugging in the documentation. Remember that there are no PDBs with managed code since managed code is compiled to ILASM; the debugger talks to the CLR to query extra information.
Points to note:
You can set a breakpoint at a managed code function only after it has been invoked at least once; because that is when it is JIT-compiled to ASM code. Keep in mind:
- Complications with function addresses and hence breakpoints:
- The CLR can discard compiled code, so function addresses may change.
- The same code may be multiply compiled if multiple app domains do not share the code. If you set a breakpoint, it gets set for the app domain of the current thread.
- Specialization of generics can cause multiple addresses for the same function.
- Complications with data layout and hence data inspection:
- The CLR may change data layout arbitrarily at runtime, so field offsets in a structure may change over time.
- Type information is loaded only on first use, so you may not be able to inspect a data field if it has not been used yet.
- Complications with debugger commands:
- When tracing through managed code, you would pass through chunks of runtime code like the JIT compiler code because you stepped into a function for the first time, or, when transitioning from managed to unmanaged code.
Debugging Services
You can debug a service just as any other application using WinDbg, both after starting the service by attaching to the service process, and, by using WinDbg as a JIT debugger and programmatically calling DbgBreakPoint or DebugBreak, or an ASM int 3 on x86.
Debugging Exceptions
A debugger gets notified of each exception twice – it is notified the first time before the application gets a chance to handle the exception (‘first chance exception’); if the application does not handle the exception, the debugger is given a chance to handle the exception ( ‘second-chance exception’). If the debugger does not handle a second-chance exception, the application quits.
.lastevent, or, !analyze –v will show you the exception record and stack trace of the function where the exception occurred.
You can also use the .exr, .cxr and .ecxr commands to display the exception and context records. Note also that you can change the first-chance handling option for an exception using the sxe, sxd, sxn and sxi commands.
WinDbg Features
Debugger Extension DLLs
Debugger extensions are DLLs that you can hook up with a debugger to execute custom commands from within the debugger. There are certain functions that a DLL needs to implement and some requirements that a DLL needs to meet in order to qualify as an extension DLL. In the next article, we shall learn how to write an extension DLL yourself. The bang (!) commands are commands executed from your extension DLLs. Note that extension DLLs are loaded in the process space of the debugger.
Dump Files
You can take snapshot information of a process using the dump facility. A mini-dump is usually small, unless you take a full-memory minidump (.dump /mf). It is useful to dump handle information also, as .dump/mfh. A mini-dump contains information about all threads including their stacks and list of loaded modules. A full dump contains more information, like that of the process heap.
Crash Dump Analysis
If your Windows OS crashes, it dumps the physical memory contents and all process information to a dump file, configured through System->Control Panel->Advanced->’Startup and Recovery’. It is also possible to take dumps of any live process by breaking into it. You can also take a dump of any process (.dump) that terminates abnormally by configuring WinDbg as a JIT debugger. Note that figuring out bugs in the code from a crash dump could be an involved process.
To analyze a dump, follow these steps:
Step 1: In WinDbg, File->’Open Crash Dump’, and point to the dump file
Step 2: WinDbg will show you the instruction your app was executing when it crashed.
Step 3: Set your symbol path and source path properly. If you cannot match symbols, you could have a hard time figuring out control flow. If you can match the symbols to source code of the appropriate version, it should be easy to figure out the bug at this point. Note that private symbol files have line number information and will blindly show the line in your source code without further checks; if your source is not version-matched properly, you’d not see the correct source code matching the assembly code. If you have public PDB files, you’ll see the last public function (on the call stack) that was invoked.
Note that debugging drivers or managed code is much different. Refer to [2] for debugging techniques for device drivers.
WinDbg Settings
Symbol Files and Directories
You need symbols in order to be able to do effective debugging. Symbol files could be in an older COFF format or the PDB format. PDBs are program database files and contain public symbols. These debuggers allow you to mention a list of URIs where they would look for symbols for loaded binaries.
OS symbols are usually installed in the %SYSTEMDIR%Symbols directory. Driver symbols (.DBG or .PDB files) are usually in the same folder as the driver (.sys file). Private symbol files contain information about functions, local and global variables, and line information to correlate assembly code to source code; symbol files that are usually made available to customers are public symbol files – these files contain information about public members only.
You can set symbol directories through File->Symbol File Path, or using .sympath from the WinDbg command window. To add reference to a symbol server on the web, add:
SRV*downstream_store*http://msdl.microsoft.com/download/symbols
to your .sympath, thus:
.sympath+ SRV*c:\tmp*http://msdl.microsoft.com/download/symbols
Where c:\tmp is the download_store where necessary symbols will be downloaded and stored. Note that this particular symbol server exposes public symbols only.
The debugger matches information like filename, timestamp and checksum when matching a PDB with a binary (DLL or exe). If you have symbol information, you’d be able to see function names and their arguments in your call stack. If the binaries and PDBs are from your application, you’d additionally have information about private functions, local variables and type information.
The sympath can consist of multiple URIs. Sympath is initialized from the _NT_SYMBOL_PATH system environment variable.
Source Code Directories
You can set source code directories through File->Source File Path, or using .srcpath from the WinDbg command window. If you set source code directories, the debugger will pull up matching source code based on line number information from the PDB files during debugging.
Breakpoints, Tracing
- Set soft breakpoints using the
bpcommands or using the toolbar breakpoint icon. - Set hard breakpoints using code like
DbgBreakPoint()orKdBreakPoint(). - Use tracing routines
DbgPrint,KdPrint,OutputDebugStringto print out to the WinDbg output window, from debugger extension DLLs.
Commands
Basic Commands
The help file that comes with the WinDbg installation documents commands well, but the following basic commands should get you started:
| Feature | Command | What Does it Do | Example / Comments | See Also Related Commands |
| Stack trace | K, KB x |
Displays stack trace of current thread (x frames). Kb causes the display to include the first three parameters passed to each function. |
KP, Kp, or KV | |
| Frame | .frame X |
|||
| Register watch | R | Displays register set. reax – displays the eax register. |
||
| Step | t | Trace = Step into (F11) | ||
| p | Step over (F10) | |||
| Step out | Shift + F11 | |||
| Disassemble | u | Unassemble next few instructions | ||
u <start_address> |
Unassemble instructions at start_address |
|||
u <start_address><end_address> |
Unassemble instructions from start_address till end_address |
|||
| Breakpoints | Bl | List breakpoints. | ||
| be, bd, bc | Enable / disable / clear breakpoint. | |||
| bp | Set a breakpoint. | |||
| bu | Set unresolved breakpoint. Breakpoint is resolved by symbolic name, not absolute address. Use this to set breakpoint at a function whose containing module has not yet been loaded. | bu foo | ||
| Comment | * | Ignores the command | * Hello World | |
| Continue | G <address_X / symbol> |
Go. Resumes execution until address_X |
||
| GH | Go, exception handled | |||
| GN | Go, exception not handled | |||
| Quit | Q | |||
| Dumping data | dv | Display local variables. | You need private symbols. | |
Dd <address> |
Display dword values at specified address. |
To see value of an int, DD <addr> L1 |
||
| Ds, da (ASCII), du (Unicode) | Dump string | |||
| Dt [dt module!typedef adr] | Dump type. Will dump the contents of the memory using typedef as a template. |
|||
| Change / Edit Values | Eb (byte), ed (dword), ea (ASCII), eu (Unicode) |
Edit value of a variable | ||
| List modules | lm | List loaded modules | Lmi, lml, !dlls | |
| Threads | ~ | Lists all threads | ||
| Command on thread n | ~n<command> |
Switch to a specific thread by thread-id and execute a command on the thread. | ~2kb (second thread’s stack) | |
| Search for a symbol in a module | X module!<pattern> | X blah!*foo* | ||
| Dump | .dump | |||
| Source line display | .lines | Turns on source code display | ||
| ln adr | Will show the symbol nearest to that location. |
- There is no “step out” (Shift+F11). You have to find the return address on the stack manually and use “g adr”. You can find this address by using “k”. If you know the function uses ebp frames you can use “g poi(ebp+4)” to step out.
- To inspect local variables:
- Use the “dv” command.
- Then use the “dt <
variablename>” command. - Note: you may not see correct values if values are stored in registers or due to FPO.
More Commands
| Feature | Command | What Does it Do | Example / Comments | See Also Related Commands |
| Vertarget | Shows information about the system on which you are debugging. | |||
| Data breakpoint (hardware bp) | Ba[ba r/w/e size adr] | Sets a data breakpoint. You can break on read/ write/ execute attempt of a memory location. | ba w4 adr | |
| Exceptions | .lastevent | Displays last exception record | ||
| Exceptions | Sx, Sxe, sxd, sxn, sxi exception_X |
Enable/ disable/ notify-only/ ignore first chance exception /event exception_X. Example of event: module unload/ thread creation. |
||
| Display type | Dt | Shows struct and field values. |
Dt x; // x: int Dt myStruct; // struct myStruct Dt myStruct myVar1; // shows myStruct.myVar1 |
|
| Reload symbols | .reload | Reloads symbols using the symbol path you would have set. | ||
| Source lines | l+l, l+o, l+s, l+t | Source line options | ||
| .ecxr | If you had an exception, switches context to faulting context. | |||
| .quit_lock | ||||
| ; | Command separator | |||
| ? | Evaluate expression | |||
| | | Display process information | |||
| .chain | Lists all loaded debugger extensions. | |||
.echo <string> |
Echo/ print any string | Echo xyz | ||
.exr <address_x> |
Display exception record at x. |
|||
.cxr <address_x> |
Display context record at x. |
|||
| .trap | Dump a trap frame. |
Handy Extension Commands
- !help – help for WinDbg extension commands.
- !load, !unload – to load and unload debugger extension DLLs.
- !handle – displays information about handles owned by processes.
- !peb - shows the PEB (process environment block) including DLL information.
Example
Attached is a sample application with these example functions:
- Example1: Program appears hung because a thread waits indefinitely on a critical section that another thread acquired and then exited without releasing.
- Example2: Exception: division by zero.
- Example3: Execute a command every time a breakpoint is hit.
- Example4: Exception: null pointer access
- Example5: Exception: double deletion
- Example6: Exception: stack overflow due to infinite recursion
Suggested Exercises
- Exception: Array out-of-bound access
- Exception: Deleted pointer access
- Exception: Stack underflow
Epilogue
Points to Note
Please note that:
- when you run WinDbg, attach to a process and issue kb, you’d be seeing the stack trace of the thread injected by the debugger. All debugging commands are executed in the context of the injected thread.
- Frame Pointer Omission (FPO):Means that when your code is compiled, frame pointers (EBP) will not be put on the stack. This makes function calls faster and makes the EBP register available as a scratch register. The optimization option /Oy in the MSC++ compiler => FPO; /O2 or /Ox (full optimization) => /Oy.
Q & A
- How can I list all symbols exported by a module?x <module>!*
- How can I find help for a specific command?.hh <command>, or <command> /?
- I want a certain application x.exe to run always under WinDbg. How can I configure this?Create a key named x.exe under “HKLM\Software\Microsoft\Windows NT\currentversion\image file execution options” and add a new string value “Debugger” to it; set its value to the path of windbg.exe.
- I want to do something every time a breakpoint is hit. How can I do that?The bp command accepts a list of commands as argument that you can execute every time a breakpoint is hit. Example:
bp WindbgEx1!Example3+0x3d "dd [ebp-0x14] L1; .echo hello world;g"
(ref. attached code)
prints the value of a local variable in each iteration of function Example3.
- Can I put a breakpoint that is triggered only once?Yes:bp /1
- Can I set a breakpoint such that it will start hitting only after k-1 passes?Yes, bp <address> k
References
- WinDbg documentation [from Microsoft]
- “The Windows 2000 Device Driver Book” – Art Baker, Jerry Lozano
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
http://www.3800hk.com/Article/aqjs/cracker/fhb/2005-08-06/Article_29972_3.html
| Win32 ASM详解-WINDOWS钩子函数 |
| 责任编辑:admin 更新日期:2005-8-6 |
| mov HookFlag,TRUE invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText .endif 该应用程序有一个全局变量,HookFlag,它用来监视钩子的状态。如果安装来钩子它就是TRUE,否则是FALSE。 当用户按下Hook按钮时,应用程序检查钩子是否已经安装。如果还没有的话,它将调用DLL中引出的函数InstallHook来安装它。注意我们把主对话框的句柄传递给了DLL,这样这个钩子DLL就可以把WM_MOUSEHOOK消息传递给正确的窗口了。当应用程序加载时,钩子DLL也同时加载。时机上当主程序一旦加载到内存中后,DLL就立即加载。DLL的入口点函数载主程序的第一条语句执行前就前执行了。所以当主程序执行时,DLL已经初始化好了。我们载入口点处放入如下代码: .if reason==DLL_PROCESS_ATTACH push hInst pop hInstance .endif 该段代码把DLL自己的实例句柄放到一个全局变量中保存。由于入口点函数是在所有函数调用前被执行的,所以hInstance总是有效的。我们把该变量放到.data中,使得每一个进程都有自己一个该变量的值。因为当鼠标光标停在一个窗口上时,钩子DLL被映射进进程的地址空间。加入在DLL缺省加载的地址处已经加载其它的DLL,那钩子DLL将要被映射到其他的地址。hInstance将被更新成其它的值。当用户按下Unhook再按下Hook时,SetWindowsHookEx将被再次调用。这一次,它将把新的地址作为实例句柄。而在例子中这是错误的,DLL装载的地址并没有变。这个钩子将变成一个局部的,您只能钩挂发生在您窗口中的鼠标事件,这是很难让人满意的 。 InstallHook proc hwnd:DWORD push hwnd pop hWnd invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL mov hHook,eax ret InstallHook endp InstallHook 函数非常简单。它把传递过来的窗口句柄保存在hWnd中以备后用。接着调用SetWindowsHookEx函数来安装一个鼠标钩子。该函数的返回值放在全局变量hHook中,将来在UnhookWindowsHookEx中还要使用。在调用SetWindowsHookEx后,鼠标钩子就开始工作了。无论什么时候发生了鼠标事件,MouseProc函数都将被调用: MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD invoke CallNextHookEx,hHook,nCode,wParam,lParam mov edx,lParam assume edx:PTR MOUSEHOOKSTRUCT invoke WindowFromPoint,[edx].pt.x,[edx].pt.y invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0 assume edx:nothing xor eax,eax ret MouseProc endp 钩子函数首先调用CallNextHookEx函数让其它的钩子处理该鼠标事件。然后,调用WindowFromPoint函数来得到给定屏幕坐标位置处的窗口句柄。注意:我们用lParam指向的MOUSEHOOKSTRUCT型结构体变量中的POINT成员变量作为当前的鼠标位置。在我们调用PostMessage函数把WM_MOUSEHOOK消息发送到主程序。您必须记住的一件事是:在钩子函数中不要使用SendMessage函数,它会引起死锁。MOUSEHOOKSTRUCT的定义如下: MOUSEHOOKSTRUCT STRUCT DWORD pt POINT <> hwnd DWORD ? wHitTestCode DWORD ? dwExtraInfo DWORD ? MOUSEHOOKSTRUCT ENDS
当主窗口接收到WM_MOUSEHOOK 消息时,它用wParam参数中的窗口句柄来查询窗口的消息。 .elseif uMsg==WM_MOUSEHOOK 它指定.bss段作为一个共享段以便所有映射该DLL的进程共享未初始化的数据段。如果不用该开关,您DLL中的钩子就不能正常工作了 |
http://www.3800hk.com/Article/aqjs/cracker/fhb/2005-08-06/Article_29972_2.html
| Win32 ASM详解-WINDOWS钩子函数 |
| 责任编辑:admin 更新日期:2005-8-6 |
| m, SWP_SHOWWINDOW .elseif uMsg==WM_MOUSEHOOK invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128 invoke wsprintf,addr buffer,addr template,wParam invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer .endif invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128 invoke GetClassName,wParam,addr buffer,128 invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer .endif invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128 invoke GetClassLong,wParam,GCL_WNDPROC invoke wsprintf,addr buffer,addr template,eax invoke lstrcmpi,addr buffer,addr buffer1 .if eax!=0 invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer .endif .elseif uMsg==WM_COMMAND .if lParam!=0 mov eax,wParam mov edx,eax shr edx,16 .if dx==BN_CLICKED .if ax==IDC_EXIT invoke SendMessage,hDlg,WM_CLOSE,0,0 .else .if HookFlag==FALSE invoke InstallHook,hDlg .if eax!=NULL mov HookFlag,TRUE invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText .endif .else invoke UninstallHook invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText mov HookFlag,FALSE invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL .endif .endif .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgFunc endp end start ;----------------------------------------------------- DLL的源代码部分 -------------------------------------- .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib include \masm32\include\user32.inc includelib \masm32\lib\user32.lib .const WM_MOUSEHOOK equ WM_USER+6 .data hInstance dd 0 .data? hHook dd ? hWnd dd ? .code DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD .if reason==DLL_PROCESS_ATTACH push hInst pop hInstance .endif mov eax,TRUE ret DllEntry Endp MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD invoke CallNextHookEx,hHook,nCode,wParam,lParam mov edx,lParam assume edx:PTR MOUSEHOOKSTRUCT invoke WindowFromPoint,[edx].pt.x,[edx].pt.y invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0 assume edx:nothing xor eax,eax ret MouseProc endp InstallHook proc hwnd:DWORD push hwnd pop hWnd invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL mov hHook,eax ret InstallHook endp UninstallHook proc invoke UnhookWindowsHookEx,hHook ret UninstallHook endp End DllEntry ;---------------------------------------------- DLL的Makefile文件 ---------------------------------------------- NAME=mousehook $(NAME).dll: $(NAME).obj Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib $(NAME).obj $(NAME).obj: $(NAME).asm ml /c /coff /Cp $(NAME).asm 分析:该应用程序的主窗口中包括三个编辑控件,它们将分别显示当前鼠标光标所在位置的窗口类名、窗口句柄和窗口过程的地址。还有两个按钮:“Hook”和“Eixt”。当您按下Hook时,应用程序将钩挂鼠标输入的事件消息,该按钮的文本将变成“Unhook”。当您把鼠标关标滑过一个窗口时,该窗口的有关消息将显示在主窗口中。当您按下“Unhook”时,应用程序将卸载钩子。 主窗口使用一个对话框来作为它的主窗口。它自定义了一个消息WM_MOUSEHOOK,用来在主窗口和DLL之间传递消息。当主窗口接收到该消息时,wParam中包含了光标所在位置的窗口的句柄。当然这是我们做的安排。我这么做只是为了方便。您可以使用您自己的方法在主应用程序和DLL之间进行通讯。 .if HookFlag==FALSE |
http://www.3800hk.com/Article/aqjs/cracker/fhb/2005-08-06/Article_29972.html
| Win32 ASM详解-WINDOWS钩子函数 |
| 责任编辑:admin 更新日期:2005-8-6 |
| 第二十四课 WINDOWS钩子函数 本课中我们将要学习WINDOWS钩子函数的使用方法。WINDOWS钩子函数的功能非常强大,有了它您可以探测其它进程并且改变其它进程的行为。 理论:WINDOWS的钩子函数可以认为是WINDOWS的主要特性之一。利用它们,您可以捕捉您自己进程或其它进程发生的事件。通过“钩挂”,您可以给WINDOWS一个处理或过滤事件的回调函数,该函数也叫做“钩子函数”,当每次发生您感兴趣的事件时,WINDOWS都将调用该函数。一共有两种类型的钩子:局部的和远程的。
安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件时都将调用您的钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。记住:功能强大也意味着使用时要负责任。
现在我们知道了一些基本的理论,现在开始讲解如何安装/卸载一个钩子。
要卸载一个钩子时调用UnhookWidowHookEx函数,该函数仅有一个参数,就是欲卸载的钩子的句柄。如果调用成功的话,在eax中返回非0值,否则返回NULL。
HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD HookProc 可以看作是一个函数名的占位符。只要函数的原型一致,您可以给该函数取任何名字。至于以上的几个参数及返回值的具体含义各种类型的钩子都不相同。譬如:
所以您必须查询您的WIN32 API 指南来得到不同类型的钩子的参数的详细定义以及它们返回值的意义。这里还有一个问题需要注意:所有的钩子都串在一个链表上,最近加入的钩子放在链表的头部。当一个事件发生时,WINDOWS将按照从链表头到链表尾调用的顺序。所以您的钩子函数有责任把消息传到下一个链中的钩子函数。当然您可以不这样做,但是您最好明白这时这么做的原因。在大多数的情况下,最好把消息事件传递下去以便其它的钩子都有机会获得处理这一消息的机会。调用下一个钩子函数可以调用函数CallNextHookEx。该函数的原型如下:
请注意:对于远程钩子,钩子函数必须放到DLL中,它们将从DLL中映射到其它的进程空间中去。当WINDOWS映射DLL到其它的进程空间中去时,不会把数据段也进行映射。简言之,所有的进程仅共享DLL的代码,至于数据段,每一个进程都将有其单独的拷贝。这是一个很容易被忽视的问题。您可能想当然的以为,在DLL中保存的值可以在所有映射该DLL的进程之间共享。在通常情况下,由于每一个映射该DLL的进程都有自己的数据段,所以在大多数的情况下您的程序运行得都不错。但是钩子函数却不是如此。对于钩子函数来说,要求DLL的数据段对所有的进程也必须相同。这样您就必须把数据段设成共享的,这可以通过在链接开关中指定段的属性来实现。在MASM中您可以这么做:
已初期化的段名是.data,未初始化的段名是.bss。`加入您想要写一个包含钩子函数的DLL,而且想使它的未初始化的数据段在所有进程间共享,您必须这么做:
S 代表该段是共享段。 例子:一共有两个模块:一个是GUI部分,另一个是安装和卸载钩子的DLL。;--------------------------------------------- 主程序的源代码部分-------------------------------------- |
Displaying Keyboard Input
2009/03/29| Platform SDK: Windows User Interface |
Displaying Keyboard Input
The example in this section shows how an application can receive characters from the keyboard, display them in the client area of a window, and update the position of the caret with each character typed. It also demonstrates how to move the caret in response to the LEFT ARROW, RIGHT ARROW, HOME, and END keystrokes, and shows how to highlight selected text in response to the SHIFT+RIGHT ARROW key combination.
During processing of the WM_CREATE message, the window procedure shown in the example allocates a 64K buffer for storing keyboard input. It also retrieves the metrics of the currently loaded font, saving the height and average width of characters in the font. The height and width are used in processing the WM_SIZE message to calculate the line length and maximum number of lines, based on the size of the client area.
The window procedure creates and displays the caret when processing the WM_SETFOCUS message. It hides and deletes the caret when processing the WM_KILLFOCUS message.
When processing the WM_CHAR message, the window procedure displays characters, stores them in the input buffer, and updates the caret position. The window procedure also converts tab characters to four consecutive space characters. Backspace, linefeed, and escape characters generate a beep, but are not otherwise processed.
The window procedure performs the left, right, end, and home caret movements when processing the WM_KEYDOWN message. While processing the action of the RIGHT ARROW key, the window procedure checks the state of the SHIFT key and, if it is down, selects the character to the right of the caret as the caret is moved.
Note that the following code is written so that it can be compiled either as Unicode or as ANSI. If the source code defines UNICODE, strings are handled as Unicode characters; otherwise, they are handled as ANSI characters.
#define BUFSIZE 65535 #define SHIFTED 0x8000 LONG APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; // handle to device context TEXTMETRIC tm; // structure for text metrics static DWORD dwCharX; // average width of characters static DWORD dwCharY; // height of characters static DWORD dwClientX; // width of client area static DWORD dwClientY; // height of client area static DWORD dwLineLen; // line length static DWORD dwLines; // text lines in client area static int nCaretPosX = 0; // horizontal position of caret static int nCaretPosY = 0; // vertical position of caret static int nCharWidth = 0; // width of a character static int cch = 0; // characters in buffer static int nCurChar = 0; // index of current character static PTCHAR pchInputBuf; // input buffer int i, j; // loop counters int cCR = 0; // count of carriage returns int nCRIndex = 0; // index of last carriage return int nVirtKey; // virtual-key code TCHAR szBuf[128]; // temporary buffer TCHAR ch; // current character PAINTSTRUCT ps; // required by BeginPaint RECT rc; // output rectangle for DrawText SIZE sz; // string dimensions COLORREF crPrevText; // previous text color COLORREF crPrevBk; // previous background color switch (uMsg) { case WM_CREATE: // Get the metrics of the current font. hdc = GetDC(hwndMain); GetTextMetrics(hdc, &tm); ReleaseDC(hwndMain, hdc); // Save the average character width and height. dwCharX = tm.tmAveCharWidth; dwCharY = tm.tmHeight; // Allocate a buffer to store keyboard input. pchInputBuf = (LPTSTR) GlobalAlloc(GPTR, BUFSIZE * sizeof(TCHAR)); return 0; case WM_SIZE: // Save the new width and height of the client area. dwClientX = LOWORD(lParam); dwClientY = HIWORD(lParam); // Calculate the maximum width of a line and the // maximum number of lines in the client area. dwLineLen = dwClientX - dwCharX; dwLines = dwClientY / dwCharY; break; case WM_SETFOCUS: // Create, position, and display the caret when the // window receives the keyboard focus. CreateCaret(hwndMain, (HBITMAP) 1, 0, dwCharY); SetCaretPos(nCaretPosX, nCaretPosY * dwCharY); ShowCaret(hwndMain); break; case WM_KILLFOCUS: // Hide and destroy the caret when the window loses the // keyboard focus. HideCaret(hwndMain); DestroyCaret(); break; case WM_CHAR: switch (wParam) { case 0x08: // backspace case 0x0A: // linefeed case 0x1B: // escape MessageBeep((UINT) -1); return 0; case 0x09: // tab // Convert tabs to four consecutive spaces. for (i = 0; i < 4; i++) SendMessage(hwndMain, WM_CHAR, 0x20, 0); return 0; case 0x0D: // carriage return // Record the carriage return and position the // caret at the beginning of the new line. pchInputBuf[cch++] = 0x0D; nCaretPosX = 0; nCaretPosY += 1; break; default: // displayable character ch = (TCHAR) wParam; HideCaret(hwndMain); // Retrieve the character's width and output // the character. hdc = GetDC(hwndMain); GetCharWidth32(hdc, (UINT) wParam, (UINT) wParam, &nCharWidth); TextOut(hdc, nCaretPosX, nCaretPosY * dwCharY, &ch, 1); ReleaseDC(hwndMain, hdc); // Store the character in the buffer. pchInputBuf[cch++] = ch; // Calculate the new horizontal position of the // caret. If the position exceeds the maximum, // insert a carriage return and move the caret // to the beginning of the next line. nCaretPosX += nCharWidth; if ((DWORD) nCaretPosX > dwLineLen) { nCaretPosX = 0; pchInputBuf[cch++] = 0x0D; ++nCaretPosY; } nCurChar = cch; ShowCaret(hwndMain); break; } SetCaretPos(nCaretPosX, nCaretPosY * dwCharY); break; case WM_KEYDOWN: switch (wParam) { case VK_LEFT: // LEFT ARROW // The caret can move only to the beginning of // the current line. if (nCaretPosX > 0) { HideCaret(hwndMain); // Retrieve the character to the left of // the caret, calculate the character's // width, then subtract the width from the // current horizontal position of the caret // to obtain the new position. ch = pchInputBuf[--nCurChar]; hdc = GetDC(hwndMain); GetCharWidth32(hdc, ch, ch, &nCharWidth); ReleaseDC(hwndMain, hdc); nCaretPosX = max(nCaretPosX - nCharWidth, 0); ShowCaret(hwndMain); } break; case VK_RIGHT: // RIGHT ARROW // Caret moves to the right or, when a carriage // return is encountered, to the beginning of // the next line. if (nCurChar < cch) { HideCaret(hwndMain); // Retrieve the character to the right of // the caret. If it's a carriage return, // position the caret at the beginning of // the next line. ch = pchInputBuf[nCurChar]; if (ch == 0x0D) { nCaretPosX = 0; nCaretPosY++; } // If the character isn't a carriage // return, check to see whether the SHIFT // key is down. If it is, invert the text // colors and output the character. else { hdc = GetDC(hwndMain); nVirtKey = GetKeyState(VK_SHIFT); if (nVirtKey & SHIFTED) { crPrevText = SetTextColor(hdc, RGB(255, 255, 255)); crPrevBk = SetBkColor(hdc, RGB(0,0,0)); TextOut(hdc, nCaretPosX, nCaretPosY * dwCharY, &ch, 1); SetTextColor(hdc, crPrevText); SetBkColor(hdc, crPrevBk); } // Get the width of the character and // calculate the new horizontal // position of the caret. GetCharWidth32(hdc, ch, ch, &nCharWidth); ReleaseDC(hwndMain, hdc); nCaretPosX = nCaretPosX + nCharWidth; } nCurChar++; ShowCaret(hwndMain); break; } break; case VK_UP: // UP ARROW case VK_DOWN: // DOWN ARROW MessageBeep((UINT) -1); return 0; case VK_HOME: // HOME // Set the caret's position to the upper left // corner of the client area. nCaretPosX = nCaretPosY = 0; nCurChar = 0; break; case VK_END: // END // Move the caret to the end of the text. for (i=0; i < cch; i++) { // Count the carriage returns and save the // index of the last one. if (pchInputBuf[i] == 0x0D) { cCR++; nCRIndex = i + 1; } } nCaretPosY = cCR; // Copy all text between the last carriage // return and the end of the keyboard input // buffer to a temporary buffer. for (i = nCRIndex, j = 0; i < cch; i++, j++) szBuf[j] = pchInputBuf[i]; szBuf[j] = TEXT(''); // Retrieve the text extent and use it // to set the horizontal position of the // caret. hdc = GetDC(hwndMain); GetTextExtentPoint32(hdc, szBuf, lstrlen(szBuf), &sz); nCaretPosX = sz.cx; ReleaseDC(hwndMain, hdc); nCurChar = cch; break; default: break; } SetCaretPos(nCaretPosX, nCaretPosY * dwCharY); break; case WM_PAINT: if (cch == 0) // nothing in input buffer break; hdc = BeginPaint(hwndMain, &ps); HideCaret(hwndMain); // Set the clipping rectangle, and then draw the text // into it. SetRect(&rc, 0, 0, dwLineLen, dwClientY); DrawText(hdc, pchInputBuf, -1, &rc, DT_LEFT); ShowCaret(hwndMain); EndPaint(hwndMain, &ps); break; // Process other messages. case WM_DESTROY: PostQuitMessage(0); // Free the input buffer. GlobalFree((HGLOBAL) pchInputBuf); UnregisterHotKey(hwndMain, 0xAAAA); break; default: return DefWindowProc(hwndMain, uMsg, wParam, lParam); } return NULL; }
Hook编程间接——拦截、注入、替换模式
2009/03/29钩子 (Hook)
Hook解释
Hook是Windows中提供的一种用以替换DOS下“中断”的系统机制,中文译为“挂钩”或“钩子”。在对特定的系统事件进行hook后,一旦发生已hook事件,对该事件进行hook的程序就会受到系统的通知,这时程序就能在第一时间对该事件做出响应。
另一解释:
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
Hook原理
每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。
钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。
系统钩子与线程钩子
SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。
线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。
系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含“钩子回调函数”的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。
几点说明:
(1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。
(2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。
(3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载。
Hook的应用模式
观察模式
最为常用,像Windows提供的SetWindowHook就是典型地为这类应用准备的。而且这也是最普遍的用法。
这个模式的特点是,在事情发生的时候,发出一个通知信息。观察者只可以查看过程中的信息,根据自己关心的内容处理自己的业务,但是不可以更改原来的流程。
如全局钩子中,经常使用的鼠标消息、键盘消息的监视等应用。金山词霸屏幕取词的功能是一个典型的应用(具体技术可以参考此类文章)。
注入模式
这个模式和观察模式最大的不一样的地方在于,注入的代码是为了扩展原始代码的功能业务。插件模式是此类模式的典型案例。
不管瘦核心的插件系统(如Eclipse)还是胖核心的插件系统(如Delphi、Visual Studio等IDE环境),其对外提供的插件接口都是为了扩展本身系统的功能的。
这种扩展的应用方式的典型特点,就是新的扩展代码和原来的代码会协调处理同类业务。
替换模式
如果针对应用目的不同,可以叫修复模式或破解模式。前者是为了修改系统中的BUG,后者是为了破解原有系统的限制。
很多黑客使用此种模式,将访问加密锁的DLL中的导出表,替换成自己的函数,这样跳过对软件的控制代码。这类应用的难点是,找出函数的参数。
这类模式的特点是,原有的代码会被新的代码所替换。
前面三个是基本模式,还有很多和实际应用相关的模式。
集权模式
此类模式的出现,大都是为了在全部系统中,统一处理某类事情。它的特点不在于注入的方式,而在于处理的模式。
这个模式,大都应用到某类服务上,比如键盘服务,鼠标服务,打印机服务等等特定服务上。通过统一接管此类服务的访问,限制或者协调对服务的访问。
比如键盘锁功能的实现,就是暂时关闭键盘的所有应用。
这类模式的特点主要会和特点服务有关联。
修复模式
替换模式的一种,这里强调的是其应用的目的是为了修复或扩展原有系统的功能。
破解模式
替换模式的一种,这里强调的是其应用的目的是为了跳过原有系统的一部分代码。如加密检测代码,网络检测代码等等。
插件模式
注入模式的一种,在系统的内部直接依靠HOOK机制进行扩展业务功能。
共享模式
这类应用中,经常是为了获取对方的数据。必然我希望获取对方系统中,所有字符串的值。可以通过替换对方的内存管理器,导出所有字符串。
这个应用比较特殊。不过其特点在于,目的是达到系统之间的数据共享。
其实现,可能是观察模式,也可能是替换模式。
VB中的Hook技术应用
一、Hook简介
Hook这个东西有时令人又爱又怕,Hook是用来拦截系统某些讯息之用,例如说,我们想
让系统不管在什么地方只要按个Ctl-B便执行NotePad,或许您会使用Form的KeyPreview
,设定为True,但在其他Process中按Ctl-B呢?那就没有用,这是就得设一个Keyboard
Hook来拦截所有Key in的键;再如:MouseMove的Event只在该Form或Control上有效,如果希望在Form的外面也能得知Mouse Move的讯息,那只好使用Mouse Hook来栏截Mouse
的讯息。再如:您想记录方才使用者的所有键盘动作或Mosue动作,以便录巨集,那就
使用JournalRecordHook,如果想停止所有Mosue键盘的动作,而放(执行)巨集,那就
使用JournalPlayBack Hook;Hook呢,可以是整个系统为范围(Remote Hook),即其他
Process的动作您也可以拦截,也可以是LocalHook,它的拦截范围只有Process本身。
Remote Hook的Hook Function要在.Dll之中,Local Hook则在.Bas中。
在VB如何设定Hook呢?使用SetWindowsHookEx()
Declare Function SetWindowsHookEx Lib ‘user32′ Alias ‘SetWindowsHookExA’ _
(ByVal idHook As Long, _
ByVal lpfn As Long, _
ByVal hmod As Long, _
ByVal dwThreadId As Long) As Long
idHook代表是何种Hook,有以下几种
Public Const WH_CALLWNDPROC = 4
Public Const WH_CALLWNDPROCRET = 12
Public Const WH_CBT = 5
Public Const WH_DEBUG = 9
Public Const WH_FOREGROUNDIDLE = 11
Public Const WH_GETMESSAGE = 3
Public Const WH_HARDWARE = 8
Public Const WH_JOURNALPLAYBACK = 1
Public Const WH_JOURNALRECORD = 0
Public Const WH_KEYBOARD = 2
Public Const WH_MOUSE = 7
Public Const WH_MSGFILTER = (-1)
Public Const WH_SHELL = 10
Public Const WH_SYSMSGFILTER = 6
lpfn代表Hook Function所在的Address,这是一个CallBack Fucnction,当挂上某个
Hook时,我们便得定义一个Function来当作某个讯息产生时,来处理它的Function
,这个Hook Function有一定的叁数格式
Private Function HookFunc(ByVal ncode As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
nCode 代表是什么请况之下所产生的Hook,随Hook的不同而有不同组的可能值
wParam lParam 传回值则随Hook的种类和nCode的值之不同而不同。
因这个叁数是一个 Function的Address所以我们固定将Hook Function放在.Bas中,
并以AddressOf HookFunc传入。至于Hook Function的名称我们可以任意给定,不一
定叫 HookFunc
hmod 代表.DLL的hInstance,如果是Local Hook,该值可以是Null(VB中可传0进去),
而如果是Remote Hook,则可以使用GetModuleHandle(‘.dll名称’)来传入。
dwThreadId 代表执行这个Hook的ThreadId,如果不设定是那个Thread来做,则传0(所以
一般来说,Remote Hook传0进去),而VB的Local Hook一般可传App.ThreadId进去
值回值如果SetWindowsHookEx()成功,它会传回一个值,代表目前的Hook的Handle,
这个值要记录下来。
因为A程式可以有一个System Hook(Remote Hook),如KeyBoard Hook,而B程式也来设一
个Remote的KeyBoard Hook,那么到底KeyBoard的讯息谁所拦截?答案是,最后的那一个
所拦截,也就是说A先做keyboard Hook,而后B才做,那讯息被B拦截,那A呢?就看B的
Hook Function如何做。如果B想让A的Hook Function也得这个讯息,那B就得呼叫
CallNextHookEx()将这讯息Pass给A,于是产生Hook的一个连线。如果B中不想Pass这讯息
给A,那就不要呼叫CallNextHookEx()。
Declare Function CallNextHookEx Lib ‘user32′ _
(ByVal hHook As Long, _
ByVal ncode As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
hHook值是SetWindowsHookEx()的传回值,nCode, wParam, lParam则是Hook Procedure
中的三个叁数。
最后是将这Hook去除掉,请呼叫UnHookWindowHookEx()
Declare Function UnhookWindowsHookEx Lib ‘user32′ (ByVal hHook As Long) As Long
hHook便是SetWindowsHookEx()的传回值。此时,以上例来说,B程式结束Hook,则换A可
以直接拦截讯息。
KeyBoard Hook的范例
Hook Function的三个叁数
nCode wParam lParam 传回值
HC_ACTION 表按键Virtual Key 与WM_KEYDOWN同 若讯息要被处理传0
或 反之传1
HC_NOREMOVE
Public hHook As Long
Public Sub UnHookKBD()
If hnexthookproc <;>; 0 Then
UnhookWindowsHookEx hHook
hHook = 0
End If
End Sub
Public Function EnableKBDHook()
If hHook <;>; 0 Then
Exit Function
End If
hHook = SetWindowsHookEx(WH_KEYBOARD, AddressOf MyKBHFunc, App.hInstance, App.ThreadID)
End Function
Public Function MyKBHFunc(ByVal iCode As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
MyKBHFunc = 0 ‘表示要处理这个讯息
If wParam = vbKeySnapshot Then ‘侦测 有没有按到PrintScreen键
MyKBHFunc = 1 ‘在这个Hook便吃掉这个讯息
End If
Call CallNextHookEx(hHook, iCode, wParam, lParam) ‘传给下一个Hook
End Function
只要将上面代码放在VB的模块中,用标准VB程序就可以了,当运行该程序后,就能拦截所有键盘操作。
深入浅出HOOKS(之陆)
2009/03/29|
Platform SDK: Interprocess Communications Monitoring System Events The following example uses a variety of thread-specific hook procedures to monitor the system for events affecting a thread. It demonstrates how to process events for the following types of hook procedures:
WH_CALLWNDPROC WH_CBT WH_DEBUG WH_GETMESSAGE WH_KEYBOARD WH_MOUSE WH_MSGFILTER
The user can install and remove a hook procedure by using the menu. When a hook procedure is installed and an event that is monitored by the procedure occurs, the procedure writes information about the event to the client area of the application’s main window.
#define NUMHOOKS 7
// Global variables
typedef struct _MYHOOKDATA { int nType; HOOKPROC hkprc; HHOOK hhook; } MYHOOKDATA;
MYHOOKDATA myhookdata[NUMHOOKS];
LRESULT WINAPI MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam) { static BOOL afHooks[NUMHOOKS]; int index; static HMENU hmenu;
switch (uMsg) { case WM_CREATE:
// Save the menu handle.
hmenu = GetMenu(hwndMain);
// Initialize structures with hook data. The menu-item // identifiers are defined as 0 through 6 in the // header file. They can be used to identify array // elements both here and during the WM_COMMAND // message.
myhookdata[IDM_CALLWNDPROC].nType = WH_CALLWNDPROC; myhookdata[IDM_CALLWNDPROC].hkprc = CallWndProc; myhookdata[IDM_CBT].nType = WH_CBT; myhookdata[IDM_CBT].hkprc = CBTProc; myhookdata[IDM_DEBUG].nType = WH_DEBUG; myhookdata[IDM_DEBUG].hkprc = DebugProc; myhookdata[IDM_GETMESSAGE].nType = WH_GETMESSAGE; myhookdata[IDM_GETMESSAGE].hkprc = GetMsgProc; myhookdata[IDM_KEYBOARD].nType = WH_KEYBOARD; myhookdata[IDM_KEYBOARD].hkprc = KeyboardProc; myhookdata[IDM_MOUSE].nType = WH_MOUSE; myhookdata[IDM_MOUSE].hkprc = MouseProc; myhookdata[IDM_MSGFILTER].nType = WH_MSGFILTER; myhookdata[IDM_MSGFILTER].hkprc = MessageProc;
// Initialize all flags in the array to FALSE.
memset(afHooks, FALSE, sizeof(afHooks));
return 0;
case WM_COMMAND: switch (LOWORD(wParam)) { // The user selected a hook command from the menu.
case IDM_CALLWNDPROC: case IDM_CBT: case IDM_DEBUG: case IDM_GETMESSAGE: case IDM_KEYBOARD: case IDM_MOUSE: case IDM_MSGFILTER:
// Use the menu-item identifier as an index // into the array of structures with hook data.
index = LOWORD(wParam);
// If the selected type of hook procedure isn’t // installed yet, install it and check the // associated menu item.
if (!afHooks[index]) { myhookdata[index].hhook = SetWindowsHookEx( myhookdata[index].nType, myhookdata[index].hkprc, (HINSTANCE) NULL, GetCurrentThreadId()); CheckMenuItem(hmenu, index, MF_BYCOMMAND | MF_CHECKED); afHooks[index] = TRUE; }
// If the selected type of hook procedure is // already installed, remove it and remove the // check mark from the associated menu item.
else { UnhookWindowsHookEx(myhookdata[index].hhook); CheckMenuItem(hmenu, index, MF_BYCOMMAND | MF_UNCHECKED); afHooks[index] = FALSE; }
default: return (DefWindowProc(hwndMain, uMsg, wParam, lParam)); } break;
// // Process other messages. //
default: return DefWindowProc(hwndMain, uMsg, wParam, lParam); } return NULL; }
/**************************************************************** WH_CALLWNDPROC hook procedure ****************************************************************/
LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szCWPBuf[256]; CHAR szMsg[16]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode, wParam, lParam);
// Call an application-defined function that converts a message // constant to a string and copies it to a buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(hwndMain);
switch (nCode) { case HC_ACTION: cch = wsprintf(szCWPBuf, “CALLWNDPROC – tsk: %ld, msg: %s, %d times “, wParam, szMsg, c++); TextOut(hdc, 2, 15, szCWPBuf, cch); break;
default: break; }
ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[CALLWNDPROC].hhook, nCode, wParam, lParam); }
/**************************************************************** WH_GETMESSAGE hook procedure ****************************************************************/
LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szMSGBuf[256]; CHAR szRem[16]; CHAR szMsg[16]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode, wParam, lParam);
switch (nCode) { case HC_ACTION: switch (wParam) { case PM_REMOVE: lstrcpy(szRem, “PM_REMOVE”); break;
case PM_NOREMOVE: lstrcpy(szRem, “PM_NOREMOVE”); break;
default: lstrcpy(szRem, “Unknown”); break; }
// Call an application-defined function that converts a // message constant to a string and copies it to a // buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(hwndMain); cch = wsprintf(szMSGBuf, “GETMESSAGE – wParam: %s, msg: %s, %d times “, szRem, szMsg, c++); TextOut(hdc, 2, 35, szMSGBuf, cch); break;
default: break; }
ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[GETMESSAGE].hhook, nCode, wParam, lParam); }
/**************************************************************** WH_DEBUG hook procedure ****************************************************************/
LRESULT CALLBACK DebugProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, wParam, lParam);
hdc = GetDC(hwndMain);
switch (nCode) { case HC_ACTION: cch = wsprintf(szBuf, “DEBUG – nCode: %d, tsk: %ld, %d times “, nCode,wParam, c++); TextOut(hdc, 2, 55, szBuf, cch); break;
default: break; }
ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[DEBUG].hhook, nCode, wParam, lParam); }
/**************************************************************** WH_CBT hook procedure ****************************************************************/
LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; CHAR szCode[128]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam, lParam);
hdc = GetDC(hwndMain);
switch (nCode) { case HCBT_ACTIVATE: lstrcpy(szCode, “HCBT_ACTIVATE”); break;
case HCBT_CLICKSKIPPED: lstrcpy(szCode, “HCBT_CLICKSKIPPED”); break;
case HCBT_CREATEWND: lstrcpy(szCode, “HCBT_CREATEWND”); break;
case HCBT_DESTROYWND: lstrcpy(szCode, “HCBT_DESTROYWND”); break;
case HCBT_KEYSKIPPED: lstrcpy(szCode, “HCBT_KEYSKIPPED”); break;
case HCBT_MINMAX: lstrcpy(szCode, “HCBT_MINMAX”); break;
case HCBT_MOVESIZE: lstrcpy(szCode, “HCBT_MOVESIZE”); break;
case HCBT_QS: lstrcpy(szCode, “HCBT_QS”); break;
case HCBT_SETFOCUS: lstrcpy(szCode, “HCBT_SETFOCUS”); break;
case HCBT_SYSCOMMAND: lstrcpy(szCode, “HCBT_SYSCOMMAND”); break;
default: lstrcpy(szCode, “Unknown”); break; }
cch = wsprintf(szBuf, “CBT – nCode: %s, tsk: %ld, %d times “, szCode, wParam, c++); TextOut(hdc, 2, 75, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[CBT].hhook, nCode, wParam, lParam); }
/**************************************************************** WH_MOUSE hook procedure ****************************************************************/
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; CHAR szMsg[16]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process the message return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, wParam, lParam);
// Call an application-defined function that converts a message // constant to a string and copies it to a buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(hwndMain); cch = wsprintf(szBuf, “MOUSE – nCode: %d, msg: %s, x: %d, y: %d, %d times “, nCode, szMsg, LOWORD(lParam), HIWORD(lParam), c++); TextOut(hdc, 2, 95, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[MOUSE].hhook, nCode, wParam, lParam); }
/**************************************************************** WH_KEYBOARD hook procedure ****************************************************************/
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, wParam, lParam);
hdc = GetDC(hwndMain); cch = wsprintf(szBuf, “KEYBOARD – nCode: %d, vk: %d, %d times “, nCode, wParam, c++); TextOut(hdc, 2, 115, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[KEYBOARD].hhook, nCode, wParam, lParam); }
/**************************************************************** WH_MSGFILTER hook procedure ****************************************************************/
LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam) { CHAR szBuf[128]; CHAR szMsg[16]; CHAR szCode[32]; HDC hdc; static int c = 0; int cch;
if (nCode < 0) // do not process message return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode, wParam, lParam);
switch (nCode) { case MSGF_DIALOGBOX: lstrcpy(szCode, “MSGF_DIALOGBOX”); break;
case MSGF_MENU: lstrcpy(szCode, “MSGF_MENU”); break;
case MSGF_SCROLLBAR: lstrcpy(szCode, “MSGF_SCROLLBAR”); break;
default: wsprintf(szCode, “Unknown: %d”, nCode); break; }
// Call an application-defined function that converts a message // constant to a string and copies it to a buffer.
LookUpTheMessage((PMSG) lParam, szMsg);
hdc = GetDC(hwndMain); cch = wsprintf(szBuf, “MSGFILTER nCode: %s, msg: %s, %d times “, szCode, szMsg, c++); TextOut(hdc, 2, 135, szBuf, cch); ReleaseDC(hwndMain, hdc); return CallNextHookEx(myhookdata[MSGFILTER].hhook, nCode, wParam, lParam); } Built on Thursday, October 12, 2000 |
深入浅出HOOKS(之叁)
2009/03/29|
关于 HOOK
[ 作者: 陆麟 添加时间: 2001-6-2 0:04:44 ]
来源:lu0.126.com
大家讨论HOOK太多了.在网络中,概凡谈论到进程控制,十有八九最后会得到一句话:写HOOK. 嘿嘿,知道HOOK运作机理的有几个呢?下面,本人就MSDN文档中没有写到的一点东西稍微讲几句. 以下讲述乃针对全局HOOK而发. 1.设置HOOK过程时返回的前一HOOK地址必须被保存到一个全局共享的内存地址段中.这个共享段的地址不是什么本进程的全局变量,而是所有进程都可以看见的变量.因为,进程级变量进在本进程内可见.当其他进程加载HOOK DLL时,HOOK DLL里的所有变量都会被RESET.这也就是说: HHOOK hk; //set and get HHOOK here return hk(); 这样的描述是不能跳转到前一HOOK的.这一点,甚至在Jeffrey Richter的经典书籍<<Advance Windows>>里都描述错了. 正确的做法是: #pragma data_seg(“dt”) HHOOK prehook=0; #pragma data_seg() 然后到VC的LINK OPITION里加上: -SECTION:dt,RWS 这样,prehook就被搞到系统中被共享了.记住,一定要给prehook初始化.否则,MS编译器的编译器会LINK错误.
2.只有使用USER32.DLL的进程才会被INJECT.所以,HOOK并不是万能的.而且,用了USER32.DLL,也不一定会被INJECT.这里有个很好的例子就是整个OS启动中第一个被启动的WIN32进程:KERNEL32.DLL.大家很奇怪,KERNEL32.DLL是个DLL,怎么也会被作为进程加载?但是事实的确是这样的,顺便给大家再上一节98启动课吧.KERNEL32.DLL作为一个独立的进程,启动时加载了MSGSRV32.EXE.而MSGSRV32.EXE又启动了SPOOL32.EXE, SPOOL32.EXE启动了MPREXE.EXE.MPREXE.EXE可不能小看.我敢担保全中国没几个人真正知道它的作用的.MPREXE.EXE不仅是网络客户端部件启动的核心,更是WIN98的SERVICE的SCM.所有的WIN98的SERVICE都是由MPREXE.EXE启动的.WIN98的SERVICE都在HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunServices里呆者哩.大家都傻眼了吧.:DDD 有一点很令人奇怪, 那就是如果MPREXE.EXE运作不正常,那么SHELL绝对起不来.SHELL却是有MSGSRV32.EXE启动的.看来MPREXE和MSGSRV32有一套内部沟通机制啊.有了SHELL,就什么都有了.其他的东西被SHELL启动就难说准了.反正80%的程序是由SHELL启动的.好了,WIN98启动暂且讲到这里.我们继续原先的话题.KERNEL32.DLL居然就无法用HOOK入侵.大家如果不信的话,就试试看吧.
3.尽管使用USER32.DLL的进程会被INJECT. 但是这里还有一个技巧,那就是HOOK DLL是在进程第一次发出USER32调用的时候才被加载. 大家又目瞪口呆了吧.:) 这也就是说,在你发出USER32调用之前, HOOK DLL根本拿你没办法. 哇,真够幽默啊.:)))由于在启动HOOK前的进程绝对都是在调用GetMessage(…)/PeekMessage(…)中,那么HOOK DLL一下子满足了加载条件了.
4.加载HOOK时,HMODULE一定要正确,否则,系统就无法正确加载HOOK过程.千万不要用0代替HMODULE,因为0代表的是EXE映象的HINSTANCE(其实就是HMODULE).
好了.今天就写到这里.此文该算本主页里又一篇经典了吧.:) |
深入浅出HOOKS(之贰)
2009/03/29|
如何获得密码窗口的内容——揭开Hook的面纱
Pan Ying(zero world)
现在的不少程序都有取得密码窗口的内容的功能,你有没有想过是如何做到这一切的呢?我有一个同学就问过我这样一个问题,当时我也回答不出来,现在我研究了视窗的Hook函数之后总算有了一定的了解。下面就有我来揭开Hook函数的神秘面纱。
先来介绍Hook的含义: Hook是指在程序正常运行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息。举例来说,当你在一个窗口上点击鼠标以后,首先接收信息的是相应的Hook函数,然后才传到相应的应用程序。
如何定义Hook函数: SetWindowsHookEx: 用来装入新的Hook函数,其参数列表为下: int idHook:装入Hook类型,有WH_MOUSE 等。 HOOKPROC lpfn:要装入的Hook函数地址。 HINSTANCE hMod:函数所在的进程,如果为全局Hook函数该函数必须在Dll中。 DWORD dwThreadId:装入哪个进程,如果为全局,则为0。 返回相应Hook标识。 HookProc: 您自定义的Hook函数,应具有的参数如下: int nCode:传入的信息类型。 WPARAM wParam:短整型参数。 LPARAM lParam:长整型参数。 要在函数中调用CallNextHookEx把信息传给下一个Hook函数。
CallNextHookEx: 用于将信息传给下一个Hook函数,需要将得到的Hook标识和相应参数传入。
UnhookWindowsHookEx: 用于将装入的Hook函数从Hook链表中剔除,应传入得到的Hook标识。
下面我们来看一个例子: 例子原码的下载:HookTest.zip 本程序在Window98和Delphi5下通过。
一、调用LoadLibrary和GetProcAddress取得函数地址。 hinstDLL := LoadLibrary(LPCTSTR( ‘hooktest.dll’)); @hkprcSysMsg:=GetProcAddress(hinstDLL, ‘MouseProc’);
二、用SetWindowsHookEx将得到的函数装入。 hhk:= SetWindowsHookEx(WH_MOUSE,@hkprcSysMsg,hinstDLL, 0);
三、Windows会将函数装入到每一个进程中。
四、每当鼠标点击,自定义的Hook函数会将点击的窗口标题传给程序的标题。 if (nCode=HC_ACTION)and(WPARAM=WM_LBUTTONDOWN) then begin MyMouse:=CPointer(lPARAM); MyHandle2:=MyMouse^.hwnd; GetMem(MyString,GetWindowTextlength(myhandle2)+1); GetWindowText(MyHandle2,MyString,GetWindowTextlength(myhandle2)+1); TempHandle:=myhandle2; while (TempHandle<>0) do begin myHandle2:=TempHandle; TempHandle:=GetParent(TempHandle); end; if (myhandle2<>0) then SetWindowText(myhandle2,MyString); FreeMem(MyString); end 就这样完成了得到密码窗口内容的功能。 |
使用消息和CBT Hook控制窗口
2009/03/29|
IntroductionWindows is essentially a message driven Operating System in the sense that, the majority of actions that take place are responses to messages sent to the main window procedure of an application. Whether you press a key, or move the mouse or drag a window the application receives messages through it’s message queue and reacts accordingly. Now this results in a rather interesting corollary that can be taken advantage of by us, developers; by sending the correct messages to a window or it’s child windows in the proper order, we can actually simulate human actions on an application. And this has it’s uses in various scenarios. Obviously the first one that comes to mind is the ability to automate a task, like for example opening a document in word, left justifying the entire text and taking a print out. But for me a more interesting usage of this technique is when it’s applied to take advantage of the Windows user interface to quickly do tasks, which might otherwise require a lot of programming calls and access to undocumented information. This includes changing various system properties, making changes through the control panel applets or even changing display properties for the desktop. In this article I will randomly select one such scenario ( a test case scenario ) and see how to go about automating the task by using some simple windows techniques like posting messages to a window, enumerating child windows and elementary CBT hooking. Test scenarioI am going to be using Windows XP Professional as my test platform and therefore my example scenario is only meaningful in an XP context. Users of other Operating Systems might have to make suitable changes to my example code snippets to get the same end result as in this article. By default, the keyboard navigation short cuts for menus are not shown in the XP operating system ( something that both puzzled and annoyed a lot of users when they first encountered this in Windows 2000 ). Having long abandoned Windows 2000, I do not remember if there was a documented way of changing this setting in 2000, other than by editing the registry or using some tweaking application, but in XP this setting can be easily changed by using the Display Properties control panel applet. All you need to do is select the Appearances tab from the Display Properties dialog box, bring up the Effects sub-window and uncheck the check box that says “Hide underlined letters for keyboard navigation until I press the Alt key”. Now I am wholly sure that this setting can probably be changed by modifying a trivial registry entry; but for the sake of this test case scenario and the article, let’s assume that we do not know how to achieve the same programmatically. The human approachLet’s see how we’d do this had we done this manually sitting in front of the machine. We’d probably have to follow these steps ( or something very similar ) :-
The solution in codeNow we need to decide what we need to do to achieve the same sequence of events through code.
Implementation detailsBringing up the Display Properties windowBOOL BringUpDisplayAppearance()
{
return reinterpret_cast<int>(ShellExecute(GetDesktopWindow(),
"open","control.exe","desk.cpl Display,@Appearance",
"",SW_SHOW )) > 32 ? TRUE : FALSE;
}
The code is quite simple and straightforward, we simply use Getting the handle to the Effects buttonHWND GetEffectsButton(HWND hWndParent)
{
HWND hWnd = NULL;
EnumChildWindows(hWndParent, EnumAppearanceChildProc,
(LPARAM)&hWnd);
return hWnd;
}
BOOL CALLBACK EnumAppearanceChildProc(HWND hwnd, LPARAM lParam)
{
TCHAR buff[512];
GetWindowText(hwnd,buff,512);
if(_tcscmp(buff,_T("&Effects...")) == 0)
{
*reinterpret_cast<HWND*>(lParam) = hwnd;
return FALSE;
}
return TRUE;
}
Using Spy++ we extract the exact text associated with the Effects button which happens to be “&Effects…” and we use this knowledge to compare the text of each child control with this text repeatedly till we get the button control we want. Getting the check box handleHWND GetMenuUnderlineCheck(HWND hWndParent)
{
HWND hWnd = NULL;
EnumChildWindows(hWndParent, EnumEffectsChildProc,
(LPARAM)&hWnd);
return hWnd;
}
BOOL CALLBACK EnumEffectsChildProc(HWND hwnd, LPARAM lParam)
{
TCHAR buff[512];
GetWindowText(hwnd,buff,512);
if(_tcsstr(buff,_T("&Hide underlined")))
{
*reinterpret_cast<HWND*>(lParam) = hwnd;
return FALSE;
}
return TRUE;
}
This is very similar to how we obtained the handle to the Effects button. The CBT hook procedureLRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam) { if(nCode == HCBT_ACTIVATE) { HWND hWnd = (HWND) wParam; TCHAR buff[512]; GetWindowText(hWnd,buff,512); if(_tcscmp(buff,_T("Effects")) == 0) { ShowWindow(hWnd,SW_HIDE); g_hWndEffects = hWnd; UnhookWindowsHookEx(g_hook); } if(_tcscmp(buff,_T("Display Properties")) == 0) { ShowWindow(hWnd,SW_HIDE); } } return 0; } The We also use the hook procedure to hide the windows that pop up, which include both the main Display Properties window as well as the Effects sub-window. The infinitesimal flash still exists and if anyone has any ideas on further reducing this, they are welcome to make suggestions. The main function ( exported )DEMODLL_API BOOL ToggleMenuUnderline(void) { HWND hWndAppearance = NULL; BOOL ret = TRUE; g_hook = SetWindowsHookEx(WH_CBT, CBTProc, g_hModule, 0); ret = BringUpDisplayAppearance(); Sleep(500);//Wait for the window to come up if(ret) { hWndAppearance = FindWindow(NULL, _T("Display Properties")); ret = hWndAppearance != NULL; if(ret) { HWND hWndEffectsButton = GetEffectsButton( hWndAppearance); ret = hWndEffectsButton != NULL; if(ret) { PostMessage(hWndEffectsButton,BM_CLICK,0,0); //Wait for the Effects window to come up while(!IsWindow(g_hWndEffects)) Sleep(100); HWND hWndCheck = GetMenuUnderlineCheck( g_hWndEffects); ret = hWndCheck != NULL; if(ret) { PostMessage(hWndCheck,BM_CLICK,0,0); PostMessage(g_hWndEffects,WM_COMMAND, IDOK,NULL); PostMessage(hWndAppearance,WM_COMMAND, IDOK,NULL); //Wait for the window to be dismissed while(IsWindow(hWndAppearance)) Sleep(100); } } } } //Final checks in case of error conditions if(IsWindow(g_hWndEffects)) PostMessage(g_hWndEffects,WM_CLOSE,0,0); if(IsWindow(hWndAppearance)) PostMessage(hWndAppearance,WM_CLOSE,0,0); return ret; } We first set up our CBT hook and then call the while(!IsWindow(g_hWndEffects)) Sleep(100); This way we avoid sleeping for too long or for too less. Now we obtain the handle to the check box using the ConclusionThe test scenario we considered was perhaps too simplistic to reveal the actual power of this technique, but when you consider that you can now do anything from your program that a user can do manually using the Windows GUI, you’ll be slowly impressed by the awesome possibilities of the technique. You can use this technique to enumerate Windows themes, change the current theme, change display settings, change system settings, automate your own applications etc. The only tool you’d need in addition to Everett is Spy++ or some such similar application. Good luck with your own message based Windows automation attempts. |







回到顶端
Back to the top