leonwxqian 发布的文章

Wireshark 2 Preview n*8字节越界读取bug

此bug已提交至官方论坛,bugtrack id 10529,https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=10529
漏洞作者:blast(http://nul.pw

事发此崩溃:

(14b4.1dd8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for F:\Program Files\Wireshark\Qt5Core.dll - 
*** WARNING: Unable to verify checksum for qtshark.exe
*** ERROR: Module load completed but symbols could not be loaded for qtshark.exe
Qt5Core!QPersistentModelIndex::row:
00000000`5f2d5bd0 488b01          mov     rax,qword ptr [rcx] ds:baadf00d`baadf00d=????????????????

(注:上面这个崩溃是从调试器启动的,所以堆上未初始化的数据是以baadf00d这个填充模式填入的,实际运行时应该是00000000`00000000,上面是越界8字节的情况)

查看崩溃附近的代码:

0:000> ub .
Qt5Core!QPersistentModelIndex::operator!=+0x58:
00000000`5f2d5bc8 cc              int     3
00000000`5f2d5bc9 cc              int     3
00000000`5f2d5bca cc              int     3
00000000`5f2d5bcb cc              int     3
00000000`5f2d5bcc cc              int     3
00000000`5f2d5bcd cc              int     3
00000000`5f2d5bce cc              int     3
00000000`5f2d5bcf cc              int     3
0:000> u .
Qt5Core!QPersistentModelIndex::row:
00000000`5f2d5bd0 488b01          mov     rax,qword ptr [rcx]
00000000`5f2d5bd3 4885c0          test    rax,rax
00000000`5f2d5bd6 7403            je      Qt5Core!QPersistentModelIndex::row+0xb (00000000`5f2d5bdb)
00000000`5f2d5bd8 8b00            mov     eax,dword ptr [rax]
00000000`5f2d5bda c3              ret
00000000`5f2d5bdb 83c8ff          or      eax,0FFFFFFFFh
00000000`5f2d5bde c3              ret
00000000`5f2d5bdf cc              int     3

崩溃发生在Qt5Core!QPersistentModelIndex::row的第一行,函数试图将第一个参数(rcx)解引用给eax时崩溃。

0:000> .frame /c 1
01 00000000`001da420 00000000`5f8755e6 qtshark+0x90612
rax=baadf00dbaadf00d rbx=00000000001da6e8 rcx=baadf00dbaadf00d
rdx=0000000002c04e00 rsi=0000000000000014 rdi=00000000001da630
rip=000000013f820612 rsp=00000000001da420 rbp=00000000001da589
 r8=0000000000008000  r9=0000000000000008 r10=0000000000350268
r11=00000000001d9d88 r12=0000000002d3f300 r13=0000000000000003
r14=0000000002d49d30 r15=0000000002d3f300
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
qtshark+0x90612:
00000001`3f820612 498b4c2448      mov     rcx,qword ptr [r12+48h] ds:00000000`02d3f348=00c4a43f01000000
0:000> dd rcx
baadf00d`baadf00d  ???????? ???????? ???????? ????????
baadf00d`baadf01d  ???????? ???????? ???????? ????????
baadf00d`baadf02d  ???????? ???????? ???????? ????????
baadf00d`baadf03d  ???????? ???????? ???????? ????????
baadf00d`baadf04d  ???????? ???????? ???????? ????????
baadf00d`baadf05d  ???????? ???????? ???????? ????????
baadf00d`baadf06d  ???????? ???????? ???????? ????????
baadf00d`baadf07d  ???????? ???????? ???????? ????????

看看r12是从哪儿传来的,上方有一个mov rcx,rax,

0:000> uf . 
qtshark+0x905e0:
00000001`3f8205e0 4053            push    rbx
00000001`3f8205e2 4154            push    r12
00000001`3f8205e4 4883ec48        sub     rsp,48h
00000001`3f8205e8 488bda          mov     rbx,rdx
00000001`3f8205eb 4c8be1          mov     r12,rcx  ;here
00000001`3f8205ee ff15c4b21100    call    qword ptr [qtshark+0x1ab8b8 (00000001`3f93b8b8)]
00000001`3f8205f4 49837c244800    cmp     qword ptr [r12+48h],0
00000001`3f8205fa 0f84a1010000    je      qtshark+0x907a1 (00000001`3f8207a1)

qtshark+0x90600:
00000001`3f820600 488bcb          mov     rcx,rbx
00000001`3f820603 ff158f931100    call    qword ptr [qtshark+0x1a9998 (00000001`3f939998)]
00000001`3f820609 488bc8          mov     rcx,rax
00000001`3f82060c ff158e931100    call    qword ptr [qtshark+0x1a99a0 (00000001`3f9399a0)]

为了验证,在函数开头下断点,重新启动程序:

0:000> g
Breakpoint 0 hit
qtshark+0x905e0:
00000001`3f3d05e0 4053            push    rbx
0:000> r
rax=000000013f515d48 rbx=0000000002aff6c0 rcx=0000000002aff6c0
rdx=000000000023a488 rsi=0000000000000014 rdi=000000000023a3d0
rip=000000013f3d05e0 rsp=000000000023a218 rbp=000000000023a329
 r8=000000000023a490  r9=000000000023a3d0 r10=000000005fb1a340
r11=000000005fa55228 r12=000000000023a3d0 r13=0000000000000003
r14=0000000002b09fe0 r15=0000000002aff6c0
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
qtshark+0x905e0:
00000001`3f3d05e0 4053            push    rbx

执行期间可以发现:

0:000> 
qtshark+0x90603:
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for F:\Program Files\Wireshark\Qt5Core.dll - 
00000001`3f3d0603 ff158f931100    call    qword ptr [qtshark+0x1a9998 (00000001`3f4e9998)] ds:00000001`3f4e9998={Qt5Core!QList<QItemSelectionRange>::front (00000000`5f962d00)}
0:000> 
qtshark+0x90609:
00000001`3f3d0609 488bc8          mov     rcx,rax
0:000> r rax
Last set context:
rax=baadf00dbaadf00d

看来是Qt5Core!QList::front 的问题,重启bp qtshark+0x90603。

让我们看一下正常的操作是什么:

Breakpoint 0 hit
qtshark+0x90603:
00000001`3f9e0603 ff158f931100    call    qword ptr [qtshark+0x1a9998 (00000001`3faf9998)] ds:00000001`3faf9998={Qt5Core!QList<QItemSelectionRange>::front (00000000`5f462d00)}
0:000> r
rax=0000000000000000 rbx=000000000030a0d8 rcx=000000000030a0d8
rdx=0000000000000000 rsi=0000000000000014 rdi=000000000030a048
rip=000000013f9e0603 rsp=0000000000309e20 rbp=0000000000309f89
 r8=0000000000008000  r9=0000000000000008 r10=00000000003e0268
r11=0000000000309788 r12=0000000002c2f440 r13=0000000000000003
r14=0000000002c39e20 r15=0000000002c2f440
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
qtshark+0x90603:
00000001`3f9e0603 ff158f931100    call    qword ptr [qtshark+0x1a9998 (00000001`3faf9998)] ds:00000001`3faf9998={Qt5Core!QList<QItemSelectionRange>::front (00000000`5f462d00)}

进入之后,

0:000> t
Qt5Core!QList<QItemSelectionRange>::front:
00000000`5f462d00 488b11          mov     rdx,qword ptr [rcx] ds:00000000`0030a0d8=80c6310500000000
0:000> 
Qt5Core!QList<QItemSelectionRange>::front+0x3:
00000000`5f462d03 48634208        movsxd  rax,dword ptr [rdx+8] ds:00000000`0531c688=00000000
0:000> t
Qt5Core!QList<QItemSelectionRange>::front+0x7:
00000000`5f462d07 488b44c210      mov     rax,qword ptr [rdx+rax*8+10h] ds:00000000`0531c690=f0c52d0500000000
0:000> r
rax=0000000000000000 rbx=000000000030a0d8 rcx=000000000030a0d8
rdx=000000000531c680 rsi=0000000000000014 rdi=000000000030a048
rip=000000005f462d07 rsp=0000000000309e18 rbp=0000000000309f89
 r8=0000000000008000  r9=0000000000000008 r10=00000000003e0268
r11=0000000000309788 r12=0000000002c2f440 r13=0000000000000003
r14=0000000002c39e20 r15=0000000002c2f440
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Qt5Core!QList<QItemSelectionRange>::front+0x7:
00000000`5f462d07 488b44c210      mov     rax,qword ptr [rdx+rax*8+10h] ds:00000000`0531c690=f0c52d0500000000
0:000> t
Qt5Core!QList<QItemSelectionRange>::front+0xc:
00000000`5f462d0c c3              ret
0:000> r
rax=00000000052dc5f0

这一次执行结果是返回了一个指针。

这是不正常的走向:

0:000> r
rax=0000000000000000 rbx=00000000001da648 rcx=00000000001da648
rdx=000007feebae9ff0 rsi=0000000000000014 rdi=00000000001da590
rip=000000013f860603 rsp=00000000001da380 rbp=00000000001da4e9
 r8=0000000000000005  r9=0000000000000069 r10=0000000000000000
r11=0000000000000002 r12=00000000027bf3f0 r13=0000000000000003
r14=00000000027c9e30 r15=00000000027bf3f0
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
qtshark+0x90603:
00000001`3f860603 ff158f931100    call    qword ptr [qtshark+0x1a9998 (00000001`3f979998)] ds:00000001`3f979998={Qt5Core!QList<QItemSelectionRange>::front (00000000`5f962d00)}
0:000> t
Qt5Core!QList<QItemSelectionRange>::front:
00000000`5f962d00 488b11          mov     rdx,qword ptr [rcx] ds:00000000`001da648=10cf6b0200000000
0:000> 
Qt5Core!QList<QItemSelectionRange>::front+0x3:
00000000`5f962d03 48634208        movsxd  rax,dword ptr [rdx+8] ds:00000000`026bcf18=01000000
0:000> 
Qt5Core!QList<QItemSelectionRange>::front+0x7:
00000000`5f962d07 488b44c210      mov     rax,qword ptr [rdx+rax*8+10h] ds:00000000`026bcf28=0df0adba0df0adba
0:000> 
Qt5Core!QList<QItemSelectionRange>::front+0xc:
00000000`5f962d0c c3              ret

由于每次操作会产生2个selection change事件,所以有问题的是第二个操作。

Qt5Core!QList<QItemSelectionRange>::front:
mov     rdx,qword ptr [rcx]
movsxd  rax,dword ptr [rdx+8]
mov     rax,qword ptr [rdx+rax*8+10h]
ret

而这个函数的整个操作就这4行。

rdx = *rcx;
rax = *(rdx+8);
return *(rdx+rax*8+0x10);

综合一下就是:

return *(*rcx+(*(rdx+8))*8+0x10);

实际执行起来是:

return *(*arg1+0x10);

或者

return *(*arg1+0x18);

//取决于选的数量

由于我们没有符号,不知道具体代表什么,但是再出问题的部分,如果执行:

0:000> r
rax=0000000000000001 rbx=000000000015a6c8 rcx=000000000015a6c8
rdx=0000000005143d90 rsi=0000000000000014 rdi=000000000015a610
rip=000000005f462d07 rsp=000000000015a3f8 rbp=000000000015a569
 r8=0000000000008000  r9=0000000000000008 r10=0000000001f30268
r11=0000000000159d68 r12=0000000002d3f220 r13=0000000000000003
r14=0000000002d49bb0 r15=0000000002d3f220
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
Qt5Core!QList<QItemSelectionRange>::front+0x7:
00000000`5f462d07 488b44c210      mov     rax,qword ptr [rdx+rax*8+10h] ds:00000000`05143da8=0df0adba0df0adba
0:000> dd rdx+10
00000000`05143da0  02d678b0 00000000 baadf00d baadf00d
00000000`05143db0  abababab abababab abababab abababab

看到好玩的了吧,这纯粹是越界访问了。那么既然选一个就是+0x8,如果可以选上更多的数据,是否就可以读到后面的0x00000040 00000000呢?我猜应该是可以的吧=v=

0:000> .cxr
Resetting default scope
0:000> dd rdx+10
00000000`05143da0  02d678b0 00000000 baadf00d baadf00d
00000000`05143db0  abababab abababab abababab abababab
00000000`05143dc0  00000000 00000000 00000000 00000000
00000000`05143dd0  00000040 00000000

Internet Explorer 完全解析 [5]

5 对前四个大类的总结
对前四个大类的总结,归纳如下,当然,全部都是用自然语言描述的。我们可以勾勒出这样一个模型:
f1.gif
当然,方框之间只是表示“有关系”,而不是“从属关系”

我们知道,CBase作为基类,提供了许多虚函数以及基础的抽象实现。 派生出来的CElement和CMarkup对CBase进行了功能上的扩充。CMarkup作为Markup语言的解析器、处理者、辅助工具而存在,CElement则是CMarkup操作管理对象的具象。

同样,CElement也是许多其他类的基类,例如Anchor、Button、RadioBox,均是由此派生而来。

CDoc则依旧作为经典的Doc-View视图中的文档部分存在着,管理着下属的数据,包括其中的CMarkup(s)、CElement(s),以及更多的例如网络状态、网页状态、网页属性、层叠样式表的管理、下载管理、内容编码管理等等。CDoc依附于宿主而存在,CView也依旧担负着View部分的责任,负责处理CDoc发来的数据,并在宿主之上建立视图。

各个类各司其职,完成了从HTML到网页展示的转换。如果你有学过MFC,或许你会对这个架构理解的十分容易。

Internet Explorer 11 实例分析 [1]

介绍了很多Internet Explorer的函数、类的东西了,让我们实战演练一下,分析一个至2014年10月3日微软尚未修复的Internet Explorer 11中存在的空指针引用问题。

...
等5天后发布

Internet Explorer 完全解析 [4]

第四章 文档的具象实现
前几个类的介绍都不是完整的,以后有需要再补充。 免责声明:本类文章全部是我的个人理解,可能存在理解错误,如有发现,敬请指正。
4.1 介绍
CDoc910类将是我们要详细叙述的最后一个类,它可以说是网页文档的具象实现,同时,它也会贯穿在所有的类中,在分析时经常会看到许多类都关联着CDoc *pDoc这样一个成员函数,让我们看一看CDoc的真身吧。

4.2 CDoc概述
CDoc(formknl)也就是传说中的根对象(root object),对应传统的文档视图结构的文档部分。有些像是MFC中的CDoc,事实上Internet Explorer中也确实还有一个CView类。CDoc作为IHTMLDocument2/3接口的一个具体的实现,它自身封装了许多与Markup有关的内容,例如创建、增删Markup等等。对Markup具体的介绍请见完全解析Internet Explorer [3]、[5]以及“对Markup的介绍[译]”。
可以极为简化地说,一个CDoc管理着一组CMarkup,而CMarkup又与一组CElement有关。这个便是前几个内容之间的内在联系。

4.3 CDoc的成员变量
CDoc的成员变量列举如下,即使在日常调试中没有私有符号,我想,这依然可以作为参考,至少通过成员变量可以了解这个类具体能做什么。

  • CDefaultElement * _pElementDefault。 指定默认元素,如果文档中没有任何可用元素的话,那么就会用到这个默认元素。
  • CMarkup * _pPrimaryMarkup。 主markup,也就是指向当前根元素的markup。
  • CEditRouter _EditRouter。 提供垂直消息路由(也就是元素层级中传递消息)的。
  • CCaret * _pCaret。判断用户是否在编辑文档时用的。
  • CDocInfo _dci。 文档信息,文档内容传输时用。
  • CElement * _pElemEditContext。存储着当前正在编辑的选区,或者就是当前选区。
  • CElement * _pElemUIActive。当前正在显示着的UI。
  • CElement * _pElemCurrent。当前获得焦点的元素,上面说的消息路由也会从这儿开始。
  • CElement * _pElemDefault。 默认元素。
  • long _lSubCurrent。用来细分当前元素功能用。
  • CElement * _pElemNext。下一个会获得焦点的元素。
  • CRect * _pRectFocus。上一个获得焦点并且被渲染的元素的区域。
  • int _cSurface/_c3DSurface。 计数器。
  • OPTIONSETTINGS * _pOptionSettings。指向当前用户可以编辑的属性,例如文字颜色等。
  • CODEPAGESETTINGS * _pCodepageSettings。 指定codepage。
  • long _icfDefault; 。默认charformat index。
  • const CCharFormat * _pcfDefault;。 默认的charformat。
  • unsigned _cInval; 调用CDoc的Invalidate的次数。
  • unsigned _cProcessingTimeout;。 脚本执行时清空分块的超时值。
  • SHORT _iWheelDeltaRemainder;。 缩放值(zDelta)
  • CStr _cstrPasteUrl;。 粘贴时会用到的url。
  • RADIOGRPNAME *_pRadioGrpName;。 Radio Box的名字。
  • LONG _lRecursionLevel;。 最大递归层次。
  • DWORD _dwHistoryIndex;。 下一个可用的历史记录index。
  • CView _view;。 View的支持。
  • long _lLastTextID; 。 TextID。
  • long __lDocTreeVersion;。 当doc关联的markup中任何元素导致树变化时,这个值会加一。仅仅改变树中文本节点的值的时候不会改变这个版本号。
  • long __lDocContentsVersion;。 任何内容的改变都会导致这个版本加一。不管是markup还是文本节点。
  • CSelectionObject * _pCSelectionObject;。 选区对象。
  • CAtomTable _AtomTable;。 存储着元素-名字(name)的映射
  • IDocHostUIHandler * _pHostUIHandler;。 宿主的整合,这个是UIHandler的接口指针。
  • IDocHostUIHandler * _pBackupHostUIHandler;。 上一个(称为主UIHandler)失败时调用这个备选的。
  • IOleCommandTarget * _pHostUICommandHandler;。 UIHandler的Command Target。
  • DWORD _dwFlagsHostInfo;。 宿主的flags
  • DWORD _dwFrameOptions;。 框架选项。
  • CStr _cstrHostCss; 。宿主下发下来的css规则。
  • CStr _cstrHostNS;。 命名空间列表,用分号分割。
  • CElement * _pElementOMCapture;。 用于处理鼠标捕获。
  • PFN_VOID_MOUSECAPTURE _pfnCapture;。 鼠标捕获事件的处理函数。
  • void * _pvCaptureObject;。 触发事件的对象。
  • CElement * _pMenuObject;。 当前调用site(ie的这个术语当成元素来看比较靠谱?)的菜单对象。
  • CTreeNode * _pNodeLastMouseOver;。 上一次触发mouseOver事件的元素。
  • long _lSubDivisionLast; 鼠标移动过的上一个区域。
  • CTreeNode * _pNodeGotButtonDown;。 捕获到鼠标按下的site。
  • HMENU _hMenuCtx;。 菜单上下文。
  • USHORT _usNumVerbs;。 上下文菜单中的verb(活动元素)
  • HWND _hwndCached;。 处理状态时负责承载的窗口。
  • ULONG _cFreeze;。 冻结计数
  • IUnknown * _punkMimeOle;。 维持MimeOle对象的计数用。(保活)
  • IStream * _pStmDirty;。 脏文档数据流,用于刷新。
  • IMoniker * _pmkName; 。 为IPersistMoniker保存的当前使用的Moniker。
  • CDwnPost * _pDwnPost;。 用于获取当前doc的推送数据。
  • CStr _cstrUrl; 。 内部使用,当前doc的base url。
  • CStr _cstrSetDomain;。 URL Host name的子集。
  • SAFETYLEVEL _safetylevel;。 当前页面的安全等级。
  • SSL_SECURITY_STATE _sslSecurity;。 当前页面安全等级,不安全,混合,安全。
  • SSL_PROMPT_STATE _sslPrompt; 。 SSL提示,允许,询问,拒绝。
  • LONG _cInSslPrompt;。 当前的提示字段指示。
  • IHlinkBrowseContext *_phlbc;。 超链接的浏览上下文,用于历史等地方。
  • DWORD _dwLoadf;。 加载标识,离线,安静,等等。
  • IUrlHistoryStg *_pUrlHistoryStg;。 历史存储(history storage)
  • CTaskLookForBookmark *_pTaskLookForBookmark;。 检查书签的task。
  • CMapElement * _pMapHead;。指向树中的map对象。
  • long _readyState;。 readystate的指示。
  • ULONG _ulProgressPos;。 进度位置。
  • ULONG _ulProgressMax;。 最大进度指示。
  • (以下挑重要的来了,这里的东西实在是太多了)
  • CDoc* _pDocParent;。 父文档的指针
  • IHTMLEditor* _pIHTMLEditor;。 选区管理器
  • CStyleSheetArray *_pHostStyleSheets;。 宿主传来的所有的样式表。

4.4 CDoc的成员函数
CDoc提供了一大堆的成员函数,在这里我们将列举出部分重要或者常用的成员函数,以供参考。

  • InitDocClass。 初始化,生成一个CDoc类,由DLL的LibMain调用。
  • CDoc::CDoc。 CDoc的构造函数,这个和CDoc::Init合起来完成了CDoc的初始化,当然,构造函数是第一个被调用的。
  • CDoc::~CDoc。 CDoc类的析构函数。
  • CDoc::CreateRoot。为当前CDoc创建一个主根元素。
  • CDoc::Init。 初始化的第二阶段。
  • CDoc::Passivate。释放对其他元素的引用,藉由可以释放主对象。
  • CDoc::UnloadContents。 释放所有资源。
  • CDoc::RunningToLoaded。 通知主根元素OS_RUNNING事件。
  • CDoc::HitTestPoint。 在指定点击位置上向CView发出请求,找到对应位置的元素。
  • CDoc::Update。 更新对应View的缓存。
  • CDoc::IsUpToDate。 获取Cache状态是否为最新。
  • CDoc::Close。 关闭当前对象。
  • CDoc::ParentFrameSite。 当当前CDoc是在frameset中呈现的话,这个会返回它的parent site,否则返回null。
  • CDoc::ParentIFrameSite。 同上,不过是iframe。
  • CDoc::BroadcastNotify。向整个树中广播通知。
  • CDoc::SetDocParent。 设置parent。

  • 有参考意义的网页 http://msdn.microsoft.com/en-us/library/aa752038%28v=vs.85%29.aspxhttp://msdn.microsoft.com/en-us/library/aa741317%28v=vs.85%29.aspxhttp://msdn.microsoft.com/en-us/library/bb508514%28VS.85%29.aspx

Internet Explorer 完全解析 [3]

由于超出博客单文章最长长度,将在这里续写第二章内容。
2.3 CElement的成员函数(续)

  • CElement::SetUniqueNameHelper。对SetIdentifierHelper的封装。 设置unique name。
  • CElement::InvalidateCollection。 将CCollectionCache中的某个元素invalidate。
  • CElement::removeAttribute。 移除属性。获取对应属性的attr dispid,然后调用removeAttributeDispid来移除。
  • CElement::OnTabIndexChange。 TabIndexChange的响应函数。将对应元素置于focus状态。
  • CElement::OnEnterExitInvalidateCollections。 当有name或者id的元素进入tree中时,重新invalidate该collection。调用参数例如CMarkup::SCRIPTS_COLLECTION(<script>)、CMarkup::FRAMES_COLLECTION(<frame> <iframe>)等。
  • CElement::DoElementNameChangeCollections。 按tagname去将所有base elements都invalidate。重建WINDOW_COLLECTION。
  • InlineEvts::Connect。将元素与events给关联起来。

2.4 小结
CElement即元素之源,它很好的囊括了元素的基础操作,例如事件处理,转发、元素遍历、排列树等等,接下来,我们将要看到的是另一个大将:CMarkup。 在第二章中,CMarkup的身影多次出现,事实上,这个类和CElement关联颇多,现在让我们看一下它的真身。

第三章
3.1 CMarkup概述
CMarkup是最基本的Markup存储结构。HTML中的Hyper-Text Markup Language中也有一个Markup,现在我们要介绍的CMarkup所指代的就是这里面的Markup。Markup一词的来源是用蓝色铅笔将作者的草稿里面的东西特别标记出来(Marking up),在这里的意思是将文本与代码区分开来的东西。

例如,HTML中,<p>可以表示paragraph段落,而<a>则表示anchor超链接锚。这些“功能性的”Markup语法使他们和一般文本(Plain Text)相区分。这个就是一个Markup。这里的CMarkup可能稍有不同,向下继续阅读便可知道差距在何处。

3.2 CMarkup的部分成员变量
CMarkup也是一个重要的类,那么它的成员变量肯定也是十分重要的,以下将介绍其,以及其相关类成员变量。
1:CMarkupScriptContext

  • CStr _cstrNamespace。这个Markup的命名空间,主markup的命名空间是“window”,非主markup的命名空间是ms__idX(id1, id2....)。
  • CScriptMethodsTable _ScriptMethodsTable。存储在window对象中暴露的dispid对应的,由脚本暴露的派遣项的dispid所组成的映射表。 比较绕口,也就是说window对象会暴露出许多dispid,而脚本解释引擎又会暴露许多派遣项的dispid,这两者之间的映射关系会组成一个映射表,它们就存储在这里。
  • CStr _cstrUrl。报告这个markup里所用脚本用的。
  • ULONG _cInlineNesting。进入或者离开内联脚本时计数用的。
  • ULONG _cScriptDownloading。 脚本下载计数。
  • DWORD _dwScriptDownloadingCookie、DWORD _dwScriptCookie。 下载cookie和脚本cookie。
  • CAryScriptEnqueued _aryScriptEnqueued。 队列等待wscript引擎执行的脚本。
  • CScriptDebugDocument * _pScriptDebugDocument。 脚本调试文档相关。
  • ULONG _idxDefaultScriptHolder。 存放这个markup中默认脚本解释引擎的脚本宿主的index。
  • BOOL _fWaitScript。为TRUE的时候表明解释器正在消息循环里等待脚本执行。

2: CMarkup
CMarkup继承于CBase。同样,它也是一个巨大无比的类,它的规模和CElement不相上下。CDoc的内容将于第四章说明,CTreePosCTreeNode两个类的内容将于第五章说明。CMarkup也是实现了大量接口,这些接口内容将在下一节介绍,现在让我们先看一看它的成员变量。

  • _LoadStatus。 加载状态
  • _pHtmCtx。 HTML上下文
  • _pProgSink。 Program Sink。
  • _OmDoc。 doc frags向脚本暴露的om document默认的dispatch。
  • _pDoc。 与该markup相关联的CDoc。
  • __lMarkupTreeVersion。 markup tree版本。
  • __lMarkupContentsVersion。 markup content版本。事实上,CDoc也有这两个成员变量,版本计算方法很简单,每调用一次UpdateMarkupTreeVersion(),这两个版本的值就会加一。关联的pDoc的两个版本号也会对应加上1。
  • _pElementRoot。 Root Element。
  • _pElementMaster。 主element。
  • _TxtArray。 存储信息的数据。
  • _pRootParseCtx。 Root的parsing context。
  • _lTopElemsVersion。 顶部元素的版本。
  • _pSelRenSvcProvider。 选区状态相关,Selection Rendering Service Provider。
  • _aryANotification。 通知数据。
  • _pmpFirst。 Markup中的指针链的第一个。
  • 伸展树(Splay Tree)相关数据
    CTreePos _tpRoot。根节点
    CTreePos * _ptpFirst。保存的第一个(最左)节点
    void * _pvPool。 pool block的列表(可被释放)
    CTreeDataPos * _ptdpFree。 free list的第一个
    BYTE _abPoolInitial [ sizeof( void * ) + TREEDATA1SIZE * INITIAL_TREEPOS_POOL_SIZE ]。 TreePos对象的初始池。

另外一提,在Internet Explorer中node的维护事实上是使用了伸展树的结构。

伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。

1nul.gif
图:元素3被访问时的伸展操作,每一步都让3向根节点靠的更近 (成员变量还没写完)

3.3 CMarkup的成员函数

  • CMarkup::CMarkup。 构造函数,接受2个参数CDoc *pDoc, CElement * pElementMaster。这两个参数分别指定了Markup所属的Doc和Markup对应的Master Element。构造函数中将设置Tree Version和Contents Version为1。
  • CMarkup::~CMarkup。 析构函数。首先,清除所有的旁视指针(lookaside pointers)。lookaside指针组成的链表将是一个双向链表。如果有StyleSheetArray,那么直接调用其CBase的PrivateRelease来释放它的引用,同时删除stylesheet的lookaside。同时释放一个pDoc的引用。
  • CMarkup::UpdateMarkupTreeVersion、CMarkup::UpdateMarkupContentsVersion 。 每调用一次,给对应版本号加一。
  • CMarkup::ClearLookasidePtrs。 字面意思,清空旁视列表指针。释放CollectionCache、释放父Markup。
  • CMarkup::Init。初始化。指定传来的CRootElement*为根元素,同时也是最右最后的元素。并且根据根元素创造一个最初的Markup。
  • CMarkup::UnloadContents。 卸载内容,删除选区服务提供者,删除伸展树,删除上下文,反注册脚本上下文、删除Range选区上下文、释放HTML上下文、卸载program sink、删除StyleSheet数组。
  • CMarkup::Passivate。 释放内容。 所有能释放的都会被释放,是UnloadContents的封装,同时还会释放Style Sheet Subobj,同时调用父类的Passivate。
  • CMarkup::DoEmbedPointers。 将未绑定的node放入伸展树。操作步骤如下:
    1、删除第一个元素,具体方法: a)获取第一个元素存一个副本,然后取得它的下一个元素; b)如果下一个元素不为空,那么把它的前一个元素置为空(不要忘了这是一个双向链表);c)将副本的前后都置为空;
    nulpw3.png
    操作情况大致如上,操作后backup还存有一个独立节点,就是之前的First,同时节点链表中不再有第一个元素。
    2、假设现在这个backup里面放着的是一个Text类型的Markup,由于Markup有顺序,如果有两个以上Markup同时指向一个Text段中间,当第一个Markup被选中后,伸展树操作会导致它出现一次split,会导致后面的Markup就会指向无效的ich,所以这里还需要判断。
    当当前指向的内容超过前一个Text长度范围时,将这个Markup的chRef一直减去前一个文本引用的cch,这样,肯定会有一刻满足chRef < cch。同时,只要文本引用还有效,就一直将文本引用向前移动(Ref的NextTreePos),这样可以确定split的右手侧,也会顺便调整这个指针。
    3、做完上述操作之后,如果当前前一个元素文本节点,同时ref的chRef小于ref的cch,这时做一次split.
    4、以backup为准,调整gravity和cling,新建一个pointerpos
    5、当backup的ref的ichref == cch时,将backup插入。这样可以保证它一直在text pos的最末尾。
    6、设置embedded为TRUE,设置ichref为0。~
  • CMarkup::GetDD。获取这个Markup的默认Dispatch对象。
  • CMarkup::EnsureScriptContext。 确保Script Context是有效的。初始化命名空间,
  • CMarkup::PrivateQueryInterface。 QueryInterface的实现。
  • CMarkup::Load。 多个重载函数都是对CMarkup::Load (HTMLOADINFO * phtmloadinfo)的封装,被封装的Load创建html context,并当Markup是主markup或者html是异步下载时会创建program sink,并将其关联到之前创建的html context上,设置URL,然后下载网页内容。
  • CMarkup::StopDownload。 停止Load函数(调用html context的SetLoad(FALSE, NULL, FALSE)),释放html context。
  • CMarkup::LoadStatus。 返回LoadStatus。
  • CMarkup::OnLoadStatus。LoadStatus变化时的回调函数,根据状态决定是否向CDoc发送消息,或是清理html context等。
  • CMarkup::EnsureTitle。 没有title时,创建一个CElement,类型为ETAG_TITLE_ELEMENT,并添加成Head Element。
  • CMarkup::AddHeadElement。 获取第一个TITLE,同时给head里的元素加引用。
  • CMarkup::SetXML。 将不认识的tag都当作xml tag处理,调用SetGenericParse(_fXML)。
  • CMarkup::PasteClipboard。 从剪贴板粘贴东西,获取剪贴板的对象,然后传到Markup对应的CDoc里,调用AllowPaste来传递剪贴板对象。
  • CMarkup::PasteUnixQuickTextToRange。 CLightDTEngine的同名函数的封装。
  • CMarkup::SetModified。 告诉CDoc数据变化了(OnDataChange)。
  • CMarkup::createTextRange。参数为(IHTMLTxtRange * * ppDisp, CElement * pElemContainer),对4个参数的createTextRange的封装,直接调用createTextRange(ppDisp, pElemContainer, NULL, NULL, TRUE)。
     
    CMarkup::createTextRange(IHTMLTxtRange * * ppDisp, CElement * pElemContainer, IMarkupPointer *pLeft, IMarkupPointer *pRight, BOOL fAdjustPointers)
    对整个文档做一次自动获取range的操作。具体操作:
    根据pElemContainer来建立一个CAutoRange对象,初始化TextRange的Lookaside列表,pLeft和pRight都设置时,对pAutoRange设置左右Markup,否则将pElemContainer作为TextRange设置到pAutoRange里。
    将pAutoRange返回到IHTMLTxtRange*中,引用计数加一。
  • CMarkup::AcceptingUndo。返回是否应该撤销。
  • CMarkup::OwningDoc。 对pDoc的主Markup进行一次QueryInterface,返回ppDoc,即所有者。
  • CMarkup::AddSegment/CMarkup::AddElementSegment/CMarkup::MovePointersToSegment/CMarkup::GetElementSegment/CMarkup::MoveSegmentToPointers/CMarkup::SetElementSegment/CMarkup::ClearSegment/CMarkup::ClearSegments/CMarkup::ClearElementSegments/CMarkup::GetSegmentCount/CMarkup::EnsureSelRenSvc/CMarkup::GetSelectionChunksForLayout/CMarkup::GetFlattenedSelection/CMarkup::HideSelection/CMarkup::ShowSelection/CMarkup::InvalidateSelection/CMarkup::IsElementSelected/CMarkup::GetSelectedElement。 选区渲染服务上下文的同名函数的封装。
  • CMarkup::GetElementTop。获取元素客户端,如果获取失败,返回Root。
  • CMarkup::EnsureTopElems。 cache顶级元素(html、head、title、frameset、body、text slaves,etc)。
  • CMarkup::MetaPersistEnabled。 遍历head的子元素,找到所有meta元素,转为CMetaElement之后调用其IsPersistMeta方法,确定针对特定的元素id,这个meta是否是一个persist meta(这是指一个可以启用或者禁用某一组控件,或者对象的标签,参见参考资料meta)。
  • CMarkup::LocateHeadMeta。 在head里面查找特定的meta。对比head的所有子meta元素,找到一个和给定的meta相同的meta。
  • CMarkup::LocateOrCreateHeadMeta。 找到一个指定meta,如果没有,创建这个meta。
  • CMarkup::FirstElement。 获得源中树的第一个位置上第一个分支的第一个元素。
  • CMarkup::GetContentTreeExtent。 获得树的区间。找到markup中第一个非root元素,到分支上最后一个元素的区间。
  • CMarkup::GetProgSinkC、CMarkup::GetProgSink。 返回_pProgSink。
  • CMarkup::IsEditable。 存在master element时返回master element的可编辑状态,否则返回文档是否处于设计模式。
  • CMarkup::InvokeEx。 InvokeEx的封装。
  • CMarkup::GetDispID。 GetDispatchID的封装。
  • CMarkup::GetNextDispID。 GetNextDispID的封装。
  • CMarkup::GetMemberName。 获取dispatch对应的成员名。
  • CMarkup::GetNameSpaceParent。 对CDoc的GetNameSpaceParent的封装。
  • CMarkup::get_designMode。 获取设计模式。
  • CMarkup::put_designMode。 设置设计模式。
  • CMarkup::open。 open的实现。
  • CMarkup::write。 write的实现。
  • CMarkup::writeln。 writeln的实现。
  • CMarkup::close。 close的实现。
  • CMarkup::clear。 clear的实现。 在ie下这是一个无操作的函数,在脚本中,调用document.open();document.close()即会清空页面内容。
  • CMarkup::get_bgColor/CMarkup::put_bgColor。 bgColor的实现。
  • CMarkup::get_fgColor/CMarkup::put_fgColor。 fgColor的实现。
  • CMarkup::get_linkColor/CMarkup::put_linkColor、alinkColor、vlinkColor。 对应属性的实现。参考linkColor/ alinkColor/ vlinkColor
  • CMarkup::get_parentWindow。 获取父窗口, parentWindow的实现。
  • CMarkup::get_activeElement。 活动元素,activeElement的实现。
  • CMarkup::get_URL/CMarkup::put_URL。 URL属性操作,URL的实现。
  • CMarkup::get_location。 location属性的实现。
  • CMarkup::get_lastModified。 lastModified属性的实现。
  • CMarkup::get_referrer。 referrer属性的实现。
  • CMarkup::get_domain/CMarkup::put_domain。 domain属性的实现。
  • CMarkup::get_readyState。 readyState属性的实现。
  • CMarkup::get_Script、CMarkup::releaseCapture、CMarkup::get_styleSheets、CMarkup::get_selection。
  • CMarkup::get_cookie、CMarkup::put_cookie、 CMarkup::get_expando、 CMarkup::put_expando。
  • CMarkup::get_charset、CMarkup::put_charset、CMarkup::get_defaultCharset、CMarkup::put_defaultCharset
  • CMarkup::get_dir、CMarkup::put_dir、CMarkup::get_mimeType、CMarkup::get_fileSize、CMarkup::get_fileCreatedDate、CMarkup::get_fileModifiedDate、CMarkup::get_fileUpdatedDate、CMarkup::get_security、CMarkup::get_protocol、CMarkup::get_nameProp。这些都是对应属性的实现,上述所有属性实现部分应当都是document的内容,可以说应当放在CDoc中,多个版本的CMarkup中有这些函数,但是并没有实现他们的功能,这一点可以在调试的时候加以区分。
  • CMarkup::toString、CMarkup::attachEvent、CMarkup::detachEvent。 对父类(super)的同名函数的封装。
  • CMarkup::recalc。 调用pDoc的recalc,重新计算所有Markup。
  • CMarkup::createTextNode。 调用pDoc的createTextNode,创建文本节点。
  • CMarkup::get_uniqueID。 调用pDoc的get_uniqueID,获取唯一id。
  • CMarkup::createElement。 根据指定的元素名来创建一个元素。
  • CMarkup::createStyleSheet。 根据指定的参数创建一个LINK或者STYLE。之后修正引用,排除那些不会有style的元素,例如title、meta等。最后,将创建的LINK或者STYLE元素绑入HEADER中。
  • CMarkup::elementFromPoint。 获取指定点上的元素。
  • CMarkup::execCommand、CMarkup::execCommandShowHelp、CMarkup::queryCommandSupported、CMarkup::queryCommandEnabled、CMarkup::queryCommandState、CMarkup::queryCommandIndeterm、CMarkup::queryCommandText、CMarkup::queryCommandValue。 暂未实现。
  • CMarkup::createDocumentFragment。 为pDoc创建一个Markup,并设置为当前Markup的子Markup。
  • CMarkup::get_parentDocument、CMarkup::get_enableDownload、CMarkup::put_enableDownload、CMarkup::get_baseUrl、CMarkup::put_baseUrl。 对应属性的get、put函数。
  • CMarkup::get_childNodes。 获取Root节点的子节点。
  • CMarkup::get_inheritStyleSheets、CMarkup::put_inheritStyleSheets。 inheritStyleSheets的get和put函数。
  • CMarkup::getElementsByName。 getElementsByName的实现,调用GetDispByNameOrID返回一个IHTMLElementCollection**。
  • CMarkup::getElementsByTagName。 getElementsByTagName的实现,根据指定的TagName返回一个IHTMLElementCollection**。
  • CMarkup::getElementById。 getElementById的实现。根据指定的id返回一个IHTMLElement**。
  • CMarkup::zoom、CMarkup::get_zoomNumerator、CMarkup::get_zoomDenominator。 缩放相关属性及操作的实现。
  • CMarkup::GetLookasidePtr。 从Doc中获取Lookaside指针。
  • CMarkup::SetLookasidePtr。 向Doc中设置Lookaside指针。
  • CMarkup::DelLookasidePtr。 删除一个Doc中的Lookaside指针。
  • CMarkup::ReplacePtr。替换两个CMarkup指针,并且给其中一个增加一个计数,被替换的那个减少一个引用计数。
  • CMarkup::SetPtr。 设置一个CMarkup为另一个CMarkup,并给被设置的CMarkup增加一个引用计数。
  • CMarkup::StealPtrSet。 调用SetPtr,同时给另一个CMarkuo减少一个引用计数。
  • CMarkup::StealPtrReplace。 调用ReplacePtr,被赋值的那个多释放一次。
  • CMarkup::ClearPtr。 置指针为空,同时减少其原指针引用对象的一次引用计数。
  • CMarkup::ReleasePtr。 释放一次引用。
    上面用到了Document Fragment类,这是一个小类,在这里也一并介绍了。
  • CDocFrag::PrivateAddRef。 对Markup调用SubAddRef。
  • CDocFrag::PrivateRelease。 对Markup调用SubRelease。
  • CDocFrag::PrivateQueryInterface。 QueryInterface的实现。
  • CDocFrag::get_document。 对Markup QueryInterface,获取IID_IHTMLDocument2对应的ppIHtmlDoc。
  • CDocFrag::GetNameSpaceParent。 对Markup QueryInterface,获取其IDispatchEx。
    只有这几个函数而已。
  • CMarkup::RegisterForDirtyRange、CMarkup::UnRegisterForDirtyRange、CMarkup::GetAndClearDirtyRange、CMarkup::OnDirtyRangeChange、CMarkup::EnsureDirtyRangeContext、。 对已经标记为dirty的range的处理服务,当一个Range内的内容发生变化时,该Range即标记为Dirty。
  • CMarkup::EnsureTopElemCache。 有顶级元素cache时,返回它,否则新建一个CMarkupTopElemCache并设置为顶级元素cache。
  • CMarkup::EnsureTextFragContext。 同上,CMarkupTextFragContext的处理函数。
  • CMarkupTextFragContext::~CMarkupTextFragContext。 CMarkupTextFragContext的析构函数,释放申请的内存资源。
  • CMarkupTextFragContext::AddTextFrag。 申请内存,并填入一个TextFrag。
  • CMarkupTextFragContext::RemoveTextFrag。 移除TextFrag,并释放所占用的空间。
  • CMarkupTextFragContext::FindTextFragAtCp。 根据一个Cp来查找对应的TextFrag。
  • CMarkup::GetTextFragCount。 获得TextFrag的大小。
  • CMarkup::GetTextFrag。 获得TextFrag。申请内存,放置TextFrag并把它最终放入树的对应位置上。
  • CMarkup::RemoveTextFrag。 删除TextFrag。
  • CMarkup::InsertTextFrag。 插入一个TextFrag。
  • CMarkup::FindTextFragFromMarkupPointer。 从一个MarkupPointer中找到一个TextFrag。
  • CMarkup::EnsureStyleSheets。 确保样式表collection的存在,如果不存在就创建一个并关联上。
  • CMarkup::ApplyStyleSheets。 应用样式表,Apply的封装。
  • CMarkup::OnCssChange。 OnCssChange的响应函数。
  • CMarkup::EnsureFormats。 确保树中所有的元素的格式都是正确的,分别调用各个元素的EnsureFormats。

3.4 小结
CMarkup是Internet Explorer中关于document的实现,我们可以看到CMarkup实现了document的几乎所有的属性、事件响应、样式表的操作,在下一章中,我们将介绍最后一个大类CDoc的具体实现细节。在下一章结束后,我们将同时给出一个Internet Explorer 11中的实例分析。

参考
Splay Tree 伸展树,http://zh.wikipedia.org/wiki/%E4%BC%B8%E5%B1%95%E6%A0%91
chRef char reference的缩写
Cch count of char的缩写
split Split(x,S):以x为界,将伸展树S分离为两棵伸展树S1和S2,其中S1中所有元素都小于x,S2中的所有元素都大于x。首先执行Find(x,S),将元素x调整为伸展树的根节点,则x的左子树就是S1,而右子树为S2。