Oct 01

WinDbg调试小记:STAThread+COM调用+Console.ReadLine=内存泄漏

这两天发现某服务器进程出现内存泄漏,遂抓dump调试之。

调试内存泄漏首先当然是!dumpheap -stat:

.....

71861128    52018      2913008 System.Threading.Thread
71862cf0    22130      2962628 System.Int32[]
0040a230      198      3034188      Free
71e7fcf8   680643     13612860 System.Threading.SafeCompressedStackHandle
05ac45cc  1412552     22600832 System.Net.IPEndPoint
05fb5690   675403     24314508 UDT.UDTSocket
71860b54   727921     34970436 System.String
718542d4  1350915     37825620 System.UInt16[]
05a7b37c  1350904     54036160 System.Net.IPAddress

.....

一看吓了一跳,Thread应该是会被垃圾回收掉的,不可能会有这么多留在堆里面。于是看看Finalize Queue:

0:000> !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 16 finalizable objects (0f751b0c->0f751b4c)
generation 1 has 4 finalizable objects (0f751afc->0f751b0c)
generation 2 has 1719 finalizable objects (0f750020->0f751afc)
Ready for finalization 1406640 objects (0f751b4c->0fcaf60c)

.....

 

100W+个对象等待Finalize,基本可以确定Finalizer thread出问题了。切到里面看看Call stack:

0:000> ~2s
eax=00000001 ebx=ffffffff ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=77b191d5 esp=049eea00 ebp=049eea6c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!ZwWaitForSingleObject+0x15:
77b191d5 c20c00          ret     0Ch
0:002> k
ChildEBP RetAddr  
049ee9fc 77611270 ntdll!ZwWaitForSingleObject+0x15
049eea6c 776111d8 kernel32!WaitForSingleObjectEx+0xbe
049eea80 770bed85 kernel32!WaitForSingleObject+0x12
049eeaa4 771b9427 ole32!GetToSTA+0xad
049eead0 771b9b4d ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x132
049eebb0 770d37b4 ole32!CRpcChannelBuffer::SendReceive2+0xef
049eec28 770be868 ole32!CAptRpcChnl::SendReceive+0xaf
049eec7c 75dd1074 ole32!CCtxComChnl::SendReceive+0x95
049eec98 75dd102b rpcrt4!NdrProxySendReceive+0x49
049eeca4 75dd0146 rpcrt4!NdrpProxySendReceive+0xb
049ef0ac 75dd11ee rpcrt4!NdrClientCall2+0x18f
049ef0d0 75d45f22 rpcrt4!ObjectStublessClient+0x90
049ef0e0 770bf163 rpcrt4!ObjectStubless+0xf
049ef168 770a2b14 ole32!CObjectContext::InternalContextCallback+0x128
049ef1b8 72dc3fb7 ole32!CObjectContext::ContextCallback+0x87
049ef204 72dc4bcc mscorwks!CtxEntry::EnterContextOle32BugAware+0x2b
049ef324 72df9aeb mscorwks!CtxEntry::EnterContext+0x325
049ef358 72df9b90 mscorwks!RCWCleanupList::ReleaseRCWListInCorrectCtx+0xc4
049ef3a8 72d2befb mscorwks!RCWCleanupList::CleanupAllWrappers+0xdb
049ef3ec 72d2be0b mscorwks!SyncBlockCache::CleanupSyncBlocks+0xec

 

Google一番后发现,如果Finalizer thread的调用里面出现ole32!GetToSTA并且卡死的话,多半由于主线程为STA并且使用了COM,然后调用了阻塞的方法。(http://support.microsoft.com/?id=828988

这程序在初始化之后就调用了Console.ReadLine,工作全部在其它线程进行。程序并没有直接调用COM,但是使用了Remoting和MAF。推测是里面隐式调用了COM,导致出现了这问题。。。

解决方法那篇KB里面有给出,这里就不说了。话说要不是会一点WinDBG,应该就找不出问题根源了吧。。。M$的东西还真是。。。

Aug 13

小记:MPlayer的承载窗口不支持透明

准确来说,是不支持WS_EX_LAYERED属性。。。如果使用的话,某些机器(据我所知主要为XP系统。。。我的Vista和Win7都没出现。。。orz)有可能会出现以下情况:

  1. 窗口闪动
  2. 画面错误(颜色/大小等)
  3. DirectX非加速输出无法使用(诡异的是加速就没问题。。。
  4. Mplayer崩溃(某台机器上用gl输出的话一定会出现。。。)

 

解决方法很简单。。。就是不要用这属性(拖走)。。。顺带连TransparencyKey和Opacity也不能用了。。。orz

已经被这问题困扰很久了。。。最崩溃的是在我的机器上完全正常。。。到其它人那才会出问题。。。搞得调试也非常麻烦。。。orz

接着还得头疼怎么把窗口四周的圆角弄回来。。。难道真的要逼我改皮肤么。。。囧

Jun 22

网易有道难题。。。止步于复赛。。。

昨晚看见第3题就囧了。。。套用某人一句话。。。这俨然不是为我等小菜准备的。。。

做好前两道题用了1小时左右。。。剩下的时间都浪费在第3道上了。。。没做完是一定的。。。结果系统测试的时候第2题挂掉了。。。orz

下来以后越想越郁闷。。。第2题估计就是漏了一个处理。。。如果直接放弃第3题的话应该是可以检查出来的。。。

只能说经验不足了。。。其实第一次参加编程比赛。。。这个成绩也是意料之内。。。下次再接再厉了。。。

第一题代码:

    Public Function getNext(ByVal A As Long) As Long


        A += 1 ' bigger than a
        Dim sA As String = A.ToString()

        Dim i As Integer
        For i = 0 To sA.Length - 2
            If sA(i) = sA(i + 1) Then
                Exit For
            End If
        Next
        If i = sA.Length - 1 Then
            Return A ' A is non repeating number
        End If
        Dim leftPart As Long = (Long.Parse(sA.Substring(0, i + 1)))
        Dim rightPart As New System.Text.StringBuilder
        Dim isZero As Boolean = True

        Dim rightFirst As Long = Long.Parse(sA(i + 1)) + 1
        If rightFirst > 9 Then
            rightFirst = 0
            leftPart = getNext(leftPart)
            Dim newA As String = leftPart & rightFirst
            If i + 2 < sA.Length Then
                newA = newA & sA.Substring(i + 2)
            End If
            Return getNext(Long.Parse(newA))
        End If

        rightPart.Append(rightFirst)
        For j As Integer = i + 2 To sA.Length - 1
            If isZero Then
                rightPart.Append("0")
            Else
                rightPart.Append("1")
            End If
            isZero = Not isZero
        Next
        Return Long.Parse(leftPart & rightPart.ToString())

    End Function

 

第二题:

 

	Public Function maximize(ByVal word As String, ByVal maxChanges As Integer) As Integer


        If word.Length = 1 Then
            Return 1
        End If
        Dim counts As IDictionary(Of Char, Integer) = CountChars(word)
        Dim chars As Char() = word.ToCharArray()
        Dim changes As Integer = 0
        For i As Integer = 0 To word.Length \ 2
            If chars(i) <> chars(chars.Length - i - 1) Then
                If changes = maxChanges Then
                    'not a palindrome
                    Return 0
                End If
                changes += 1
		' 这里我忘记修正字符数了。。。应该就是这个原因而挂掉了。。。迟一点要去平台再试试。。。
                If counts(chars(i)) > counts(chars(chars.Length - i - 1)) Then
                    chars(chars.Length - i - 1) = chars(i)
                Else
                    chars(i) = chars(chars.Length - i - 1)
                End If
            End If
        Next
        If changes = maxChanges Then
            Return Score(New String(chars))
        ElseIf (chars.Length And 1) = 1 Then
            Return 1
        Else
            Return maximize(New String(chars, 0, chars.Length \ 2), maxChanges - changes) + 1
        End If
    End Function

    Private Function CountChars(ByVal word As String) As IDictionary(Of Char, Integer)
        Dim charCounts As New SortedList(Of Char, Integer)
        For Each ch As Char In word
            If charCounts.ContainsKey(ch) Then
                charCounts(ch) += 1
            Else
                charCounts(ch) = 1
            End If
        Next
        Return charCounts
    End Function

    Private Function Score(ByVal word As String) As Integer
        For i As Integer = 0 To word.Length \ 2
            If word.Chars(i) <> word.Chars(word.Length - i - 1) Then
                Return 0 ' not a palindrome
            End If
        Next
        If (word.Length And 1) = 1 Then
            Return 1 ' odd length
        Else
            Return Score(word.Substring(word.Length \ 2)) + 1
        End If
    End Function
May 12

杂记两则

很久没有发文了。。。于是记一下最近捣鼓项目的经验。。。

1、缩小Mplayer体积

Mplayer默认编译版有10MB之巨。。。其实有很多功能是不需要的,特别是我这个项目定死了视频的编码,更是可以把没用的编码全部禁用。要点有两个:

(1) FFMpeg单独编译

Mplayer虽然内置了FFmpeg,但是配置非常不方便,而且没用的功能禁用不干净。。。单独编译的时候,只需要几行参数就可以禁掉所有东西,在后面再启用需要的功能就可以了。

编译的时候要注意,光禁用_a结尾启用_so结尾的参数还不够,要手动指定库路径才能正常链接(也可能是我这的MinGW有问题。。。)。如:

--disable-libavutil_a   \
--disable-libavcodec_a   \
--disable-libavformat_a  \
--disable-libpostproc_a   \
--disable-libswscale_a    \
--enable-libavutil_so   \
--enable-libavcodec_so   \
--enable-libavformat_so  \
--enable-libpostproc_so   \
--enable-libswscale_so    \
--extra-libs-mplayer="/local/lib/libavformat.a /local/lib/libavcodec.a /local/lib/libswscale.a /local/lib/libpostproc.a /local/lib/libavutil.a" \

另外库的顺序非常重要,错了链接就会不成功。(想当初我试了N次才试出这个顺序。。。

(2) 编译后删除所有内嵌符号

这个是我最近才发现的,原来gcc会在输出文件中内嵌调试符号。。。直接导致程序大幅膨胀。这些符号基本是没有用处的(很少会有人在Windows用gdb调试吧。。。),完全是浪费空间。

还好删除符号也很简单,在Cygwin或MinGW环境下运行以下命令就可以了:

strip --strip-all <输出文件名>

弄完这些之后,MPlayer就只剩下5MB左右了,足足缩小了50%,而且基本的功能也不会缺少。

 

 

2、使Mono支持加载路径包含非ASCII字符的程序集

于是这个应该是很老的bug了,但是修复起来很简单。。。patch已经发到mono开发组了,希望下个版本可以包含吧。

2.4的Patch:

--- E:/image-2.4.c	四 四月 30 15:39:45 2009
+++ E:/image.c	四 四月 30 15:46:49 2009
@@ -878,11 +878,23 @@
 	MonoImage *image;
 	FILE *filed;
 	struct stat stat_buf;
-
+#ifdef PLATFORM_WIN32
+	gsize bytes_written;
+	gchar *fname_ansi = g_locale_from_utf8 (fname, -1, NULL, &bytes_written, NULL);
+	filed = fopen (fname_ansi, "rb");
+	g_free (fname_ansi);
+	if (filed == NULL){
+#else
 	if ((filed = fopen (fname, "rb")) == NULL){
+#endif // PLATFORM_WIN32
 		if (IS_PORTABILITY_SET) {
 			gchar *ffname = mono_portability_find_file (fname, TRUE);
 			if (ffname) {
+#ifdef PLATFORM_WIN32
+				gchar *ffname_utf8 = ffname;
+				ffname = g_locale_from_utf8 (ffname_utf8, -1, NULL, &bytes_written, NULL);
+				g_free (ffname_utf8);
+#endif // PLATFORM_WIN32
 				filed = fopen (ffname, "rb");
 				g_free (ffname);
 			}

 

由于在SVN的代码中fopen的调用改到另一个地方了,所以需要另一个patch:

--- E:/mono-filemap.c	三 四月 29 20:05:26 2009

+++ E:/mono-filemap-2.c	三 四月 29 20:11:35 2009

@@ -25,7 +25,17 @@

 MonoFileMap *
 mono_file_map_open (const char* name)
 {
-	return (MonoFileMap *)fopen (name, "rb");
+#ifdef PLATFORM_WIN32
+	// fopen in msvcrt.dll accepts ANSI string, but name is UTF-8 encoded
+	gsize bytes_written;
+	gchar *name_utf8 = name;
+	name = g_locale_from_utf8 (name_utf8, -1, NULL, &bytes_written, NULL);
+#endif // PLATFORM_WIN32
+	MonoFileMap *map = (MonoFileMap *)fopen (name, "rb");
+#ifdef PLATFORM_WIN32
+	g_free (name);
+#endif // PLATFORM_WIN32
+	return map;
 }
 
 guint64 

 

应用之后参照Mono主页的教程在Cygwin编译就可以了。(编译好要记得删符号。。。不然mono.dll会比发布版大N倍。。。

Mar 12

I264项目开发流水帐

于是认证考试终于告一段落了,前段时间接下来的项目也终于可以上马了。。。第一次做这么大的项目,正好可以实践之前学到的知识。。。

在这里记录下这次开发的进程,留待以后总结经验。。。

项目一句话简介:P2P视频播放器及服务器端

3.11-3.12:客户端组件关系图

这次把播放器的各个部分都组件化,期望能达到各个组件可以随意替换的效果。。。顺便练习了一下UML。。。

3.12:接口设计

3.13-3.15:播放器核心(其实就是MPlayer的包装。。。

3.16-3.18:网络传输库测试

网络传输部分计划使用修改过的BT协议,使用UDP传输。于是搞了几个udp可靠传输的框架下来测试。。。最终选定了UDT。测试的时候还发现默认的阻塞处理有问题,带宽浪费十分严重,有一次还把路由搞挂了。。。捣鼓了半天换上了TCP的阻塞控制算法,顺便还把keep-alive包的发送间隔改了。。。

3.18-3.19:数据转接层

3.20-3.24:MonoTorrent的UDT支持

弄这东西的时候发现UDT的Wrapper有一大堆bug。。。另外连那个TCP算法类也有bug。。。调试死了很多脑细胞。。。orz

3.25-3.27:网络传输组件

基本就是把bt组件封装一下,实现预先设计好的接口。看起来很简单,但是弄的时候才发现很麻烦。。。首先是发现mplayer的连接超时值是硬编码在代码里面的(BS啊。。。),在缓冲的时候就直接断掉了,逼着我改源代码。。。然后就是获取MonoTorrent下载好数据的代码弄了很久,最后发现直接FileStream打开就可以了。。。开始的时候想得太复杂了。。。

3.27-3.31:部分bug修复及改进

4.1-4.5:内网穿透

4.5-4.7:测试版准备

4.8-4.9:Mono兼容性修复。。。

之前以为mono就算不比.net原生慢,最多也是差不多速度。但测试下来发现窗体的响应速度mono居然要比原生.net还要快。。。算个意外收获吧,可是兼容性依然是个问题。。。最麻烦的一个问题是程序退出的时候90%会崩溃,10%进程无法退出。。。跟踪来跟踪去只发现貌似和线程池有关。。。为了不影响进度,只好暂时在窗体关闭的时候强制退出进程了。迟点再看看有没有办法修复。。。

4.9:内测版发布

4.10:修复mono引导器的bug。。。

4.12:修复退出时崩溃的bug

之前死活找不出哪里有问题,只好编译个mono来调试了。。。

Update: 搞了半天崩溃原来是mono跟踪的bug。。。进程残留是没有清理完成。。。orz

4.13-4.14:bug修复

4.15-4.20:播放器界面组件化及进程分离

进程的远程调用使用了Remoting,然后远程调用的类里有事件,于是事情就混乱了。。。搞了很多天。。。最终使用了双层包装来解决。。。迟一点等有空写一下实现细节吧。。。orz

4.21-4.25:播放器辅助功能

4.25:alpha 2发布

5.1:红眼病终于好点了。。。于是继续弄。。。

前两天修复了mono没法在中文路径下启动的bug,顺便提交到了官方。其实这个bug修复很简单,很奇怪为什么这么久都没人修复。。。

5.5:Alpha3发布

Jun 09

泪流满面。。。LibSSA终于写好了

每天只能弄一点。。。结果弄了3个月。。。囧

然后字体精简工具终于能继续了。。。话说那个bug的解决方法之前就想好了,这个库就是为了里面的配套工具写的,原本以为会很快,结果拖了这么久。。。

顺便把解决方法写一下吧。说起来其实很简单,把字体名字改变就可以了。然后再把字幕文件里的字体名字改掉就ok了。

Feb 20

火星可能。。。使reflector反编译.Net系统程序集时显示局部变量名

刚刚才发现reflector能够解析pdb的内容。。。于是出现了此文。

方法很简单。。。先用NetMassDownloader下载好所有符号文件,然后复制到对应目录就可以了(2.0的复制到%SystemRoot%\Microsoft.net\Framework\v2.0.50727,3.0/3.5的复制到%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.0(or v3.5)。

Feb 19

发现内嵌精简字体的一点问题

这几天发现了一个问题,Haali分离器加载内嵌字体后如果不正常退出会导致字体残留在内存,后续的进程所有加载同一种字体的操作都会被系统忽略,直接使用之前的字体。这就导致了如果后面加载的字体内含前面的字体没有包含的字符的话,这些字符就无法显示了。

目前的解决方法只有在Haali分离器不正常退出后注销一下,就能够恢复正常。等我有空了再研究下怎么彻底解决这个问题。。。