windbg+pykd入门

  • A+
所属分类:逆向工程

windbg+pykd入门


☆ pykd简介

传统windbg插件是用C开发的,jsprovider.dll允许用JavaScript写windbg插件,pykd.pyd则允许用Python写windbg插件,pykd同时支持Python 2.x/3.x。

先统一一下术语:

windbg+pykd入门


本文测试用例所涉及的组件如下:

x64/windbg 10.0.19041.1
x64/Python 3.9 (便携版)
x64/pykd 0.3.4.15
x64/pykd-ext 2.0.0.24

☆ 编译pykd源码

https://githomelab.ru/pykd/pykd

这个链接上作者说了,安装git、cmake,用Visual Studio 2017打开pykd.sln。这应该是最简编译方案。没有实际测试,因为没有安装VS 2017,不知VS 2017社区版行不行,估计是可以的。

Visual Studio 2017 社区版
https://visualstudio.microsoft.com/zh-hans/vs/older-downloads/
https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Community&rel=15

https://cmake.org/
https://cmake.org/download/

https://git-scm.com/

如果有VS 2017,就老老实实用它吧,别瞎折腾其他版本,没必要。git、cmake应该出现在PATH环境变量中。

我用VS 2019社区版编译成功。

9) 修正pykd.pyd处理sys.argv[]的BUG

pykd.pyd处理sys.argv[]的代码有BUG,导致给some.py指定的参数不能稳定传入;后来调试分析找到BUG源,通过修改pykd.pyd源码解决了BUG。

!pykd.pyd.py sys_argv.py "12345678"
!pykd.pyd.py sys_argv.py "1234567"

起初发现上述两条命令有重大差别,前者的sys.argv[1]是"12345678",后者的sys.argv[1]是""。黑盒测试有个初步结论,当len(sys.argv[i])小于8(i>0)时,some.py看到的sys.argv[i]是空串,后来调试分析表明这只是不可靠的表象结论,换个测试环境该黑盒测试结论可能就不成立。

不直接用pykd.pyd,换用pykd-ext,如下两条命令看到的sys.argv[1]符合预期:

!pykd.py sys_argv.py "12345678"
!pykd.py sys_argv.py "1234567"

参看:

pykdpykdwindbgext.cpp

KDLIB_EXT_COMMAND_METHOD_IMPL(PykdExt, py)
{
...
/*
 * 取some.py全路径
 */

        std::string  scriptFileName = getScriptFileName(args[0]);

#if PY_VERSION_HEX >= 0x03000000

        //
        wchar_t  **pythonArgs = new wchar_t* [ args.size() ];

        std::wstring  scriptFileNameW = _bstr_t(scriptFileName.c_str());

        pythonArgs[0] = const_cast<wchar_t*>(scriptFileNameW.c_str());

        for (size_t i = 1; i < args.size(); ++i)
        {
/*
 * argw的作用域仅限于这个for循环
 */

            std::wstring  argw = _bstr_t(args[i].c_str());
/*
 * pythonArgs[i]引用的对象在离开for循环后将被析构
 */

            pythonArgs[i] = const_cast<wchar_t*>(argw.c_str());
        }
/*
 * 此处的pythonArgs[]引用了已被释放的内存残像
 */

        PySys_SetArgv( (int)args.size(), pythonArgs );

        delete[]  pythonArgs;

#else

注释中已指明BUG源头所在,变量作用域相关。最初审看此段代码并未意识到BUG,只好编译Debug版pykd.pyd,动态调试发现给pythonArgs[i]赋值时sys.argv[i]还是正常的,但断在python39!PySys_SetArgv()时,pythonArgs[i]已经变成空串或其他乱七八糟的内容,打算用数据断点看到底谁改变了pythonArgs[i]。在IDA中查看附近的汇编代码时看到for循环尾部有调用std::wstring::~wstring(),才意识到内存释放后继续使用的BUG。下面是一个简单修正:

KDLIB_EXT_COMMAND_METHOD_IMPL(PykdExt, py)
{
...
/*
 * 取some.py全路径
 */

        std::string  scriptFileName = getScriptFileName(args[0]);

#if PY_VERSION_HEX >= 0x03000000

        //
        wchar_t  **pythonArgs = new wchar_t* [ args.size() ];

        std::wstring  scriptFileNameW = _bstr_t(scriptFileName.c_str());

        pythonArgs[0] = const_cast<wchar_t*>(scriptFileNameW.c_str());

        std::vector<std::wstring>   argws(args.size());

        for (size_t i = 1; i < args.size(); ++i)
        {
            argws[i] = _bstr_t(args[i].c_str());
            pythonArgs[i] = const_cast<wchar_t*>(argws[i].c_str());
            // printf("[%ls]n", pythonArgs[i]);
        }

        PySys_SetArgv( (int)args.size(), pythonArgs );

        delete[]  pythonArgs;

#else

pykd作者推荐通过pykd-ext使用pykd,而pykd-ext处理sys.argv[]的代码无此BUG,结果pykd的这个BUG一直隐藏至今,太坑了。

☆ 编译pykd-ext源码

$ git clone https://githomelab.ru/pykd/pykd-ext.git pykd-ext

用VS 2019社区版打开pykd_ext.sln,Build,自动下载依赖源码,比如:

pykd-extpackages
pykd-extpackagesboost.1.67.0.0

相比编译pykd,编译pykd-ext完全没幺蛾子,直接成功。

pykd-extoutx64Releasepykd.dll
pykd-extoutx64Releasepykd_ext_2.0.pdb

2) 修改pykd-ext使之支持从pykd子目录加载.py

"!pykd.pyd.py"找命令行上的some.py时认python39._pth中的路径,可以不指定目录名,只指定文件名。"!pykd.py"找命令行上的some.py时不认python39._pth中的路径。

先看pykd.pyd为什么认python39._pth中的路径,参看:

pykdpykdwindbgext.h
pykdpykdwindbgext.cpp

void PykdExt::setUp()
{
...
    Py_Initialize();
...
    python::object       sys = python::import("sys");
...
/*
 * 取sys.path
 */

    python::list pathList = python::extract<python::list>(sys.attr("path"));

    python::ssize_t  n = python::len(pathList);

    for (python::ssize_t i = 0; i < n ; i++)
/*
 * 成员变量m_paths存放sys.path
 */

        m_paths.push_back(boost::python::extract<std::string>(pathList[i]));
...
}

/*
 * 该函数用于定位some.py
 */

std::string PykdExt::getScriptFileName( const std::string &scriptName )
{
    std::string scriptFileName = findScript( scriptName );

    if ( scriptFileName.empty() )
    {
        std::string scriptNameLow;
        scriptNameLow.resize( scriptName.size() );
        std::transform(
            scriptName.begin(),
            scriptName.end(),
            scriptNameLow.begin(),
            ::tolower);
        if ( scriptNameLow.rfind(".py") != (scriptNameLow.length() - 3) )
            scriptFileName = findScript( scriptName + ".py" );
    }

    return scriptFileName;
}

/*
 * 该函数用于定位some.py
 */

std::string PykdExt::findScript( const std::string &fullFileName )
{
    if ( GetFileAttributesA(fullFileName.c_str()) != INVALID_FILE_ATTRIBUTES )
        return fullFileName;
/*
 * 如果指定相对路径,在sys.path中依次寻找some.py
 */

    std::vector<std::string>::const_iterator it = m_paths.begin();
    for ( ; it != m_paths.end(); ++it)
    {
        DWORD bufSize = SearchPathA(

pykd.pyd定位some.py的代码逻辑认可sys.path的设置,所以认python39._pth中的路径。

pykd-ext同样有个getScriptFileName()用于定位some.py,参看:

pykd-extsourceswindbgext.cpp

/*
 * 该函数用于定位some.py
 */

std::string getScriptFileName(const std::string &scriptName)
{
    char*  ext = ".py";

    DWORD searchResult = SearchPathA(
        NULL,
        scriptName.c_str(),
        ext,
        0,
        NULL,
        NULL);

    if ( searchResult == 0 )
    {
        return "";
    }

    std::vector<char>  pathBuffer(searchResult);

    searchResult =
        SearchPathA(
            NULL,
            scriptName.c_str(),
            ext,
            pathBuffer.size(),
            &pathBuffer.front(),
            NULL );

    return std::string(&pathBuffer.front(), searchResult);
}

pykd-ext的getScriptFileName()没有用到sys.path,故不认python39._pth中的路径。

让pykd-ext支持sys.path的话,动静太大,但可以小改使之从pykd.dll所在目录的pykd子目录中寻找some.py。

/*
 * added by scz
 */

static int PrivateGetCurrentModulePath char *path, int pathlen )
{
    HMODULE hm  = NULL;

    if
    (
        GetModuleHandleExA
        (
            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
            (LPCSTR)&PrivateGetCurrentModulePath,
            &hm
        ) == 0
    )
    {
        int ret = GetLastError();
        fprintfstderr"GetModuleHandleEx() failed, error = %dn", ret );
        return-1 );
    }
    if ( 0 == GetModuleFileNameA( hm, path, pathlen ) )
    {
        int ret = GetLastError();
        fprintfstderr"GetModuleFileName() failed, error = %dn", ret );
        return-1 );
    }
    char   *p   = strrchr( path, '\' );
    if ( p != NULL )
    {
        *p  = '';
    }
    return0 );
}  /* end of PrivateGetCurrentModulePath */

/*
 * added by scz
 */

std::string getScriptFileName2(const std::string &scriptName)
{
    char    path[MAX_PATH];

    if ( 0 == PrivateGetCurrentModulePath( path, sizeof( path ) ) )
    {
        // printf( "%sn", path );
        char   *infix   = "\pykd\";
        char   *suffix  = (char*)scriptName.c_str();
        if ( strlen( path ) + strlen( infix ) + strlen( suffix ) + 1 <= MAX_PATH )
        {
            std::stringstream   pypath;
            pypath << path << infix << suffix;
            char               *pypath2 = (char*)pypath.str().c_str();
            // printf( "%sn", pypath2 );

            char*  ext = ".py";

            DWORD searchResult = SearchPathA(
                NULL,
                pypath2,
                ext,
                0,
                NULL,
                NULL);

            if ( searchResult == 0 )
            {
                return "";
            }

            std::vector<char>  pathBuffer(searchResult);

            searchResult =
                SearchPathA(
                    NULL,
                    pypath2,
                    ext,
                    pathBuffer.size(),
                    &pathBuffer.front(),
                    NULL );

            return std::string(&pathBuffer.front(), searchResult);
        }
    }

    return "";

}  /* end of getScriptFileName2 */

/*
 * modified by scz
 */

std::string getScriptFileName(const std::string &scriptName)
{
    char*  ext = ".py";

    DWORD searchResult = SearchPathA(
        NULL,
        scriptName.c_str(),
        ext,
        0,
        NULL,
        NULL);

    if ( searchResult == 0 )
    {
        // return "";
        return getScriptFileName2( scriptName );
    }
...

简单改一下,对付着用。

☆ 安装pykd

网上有很多pykd的安装介绍,很容易绕晕。主要是这个文件,pykd.pyd,不管你用什么办法搞到这个文件,搞到后放到windbg的winext目录,就算安装完成。

1) pip获取pykd.pyd

python.exe -m pip install pykd --upgrade
python.exe -m pip list
python.exe -m pip show pykd

这样拖回来的pykd.pyd位于:

<Python>Libsite-packagespykdpykd.pyd

2) 从pypi.org下载

https://pypi.org/project/pykd/#files

这里有一堆.whl文件,需要找对应版本,上例是"x64+Python 3.9+0.3.4.15 pykd"。用7-Zip打开.whl文件,从中析取pykd.pyd。此法本质上同"pip install"。

3) 从githomelab.ru下载(官方发布)

https://githomelab.ru/pykd/pykd/-/wikis/home
https://githomelab.ru/pykd/pykd/-/wikis/All%20Releases
https://githomelab.ru/pykd/pykd/-/wikis/0.3.4.15

Browse 0.3.4.15 dir
https://yadi.sk/d/Ia71qisldISyyw?w=1

这里提供pykd-0.3.4.15-cp39-win-amd64.zip下载

https://yadi.sk/d/WLrzu_psR_n40Q

这里提供pykd-0.3.4.15-symbols.zip下载,就是各个版本的.pdb文件。

从.zip中析取pykd.pyd。

4) pykd-ext

https://githomelab.ru/pykd/pykd-ext

pykd_ext_2.0.0.24.zip中有个pykd.dll,把它放到windbg的winext目录。这也是一个windbg插件,但它不是pykd本身,这是pykd-ext。

pykd-ext不是pykd,没有pykd-ext,照样用pykd。

pykd-ext的使用场景是,假设已安装各种版本的Python,不是便携版,是写过注册表的安装版。

.load pykd
!pykd.help
!pykd.info
!pykd.select -3
!pykd.py
!pykd.py -2
!pykd.py -3
!pykd.py -3.9 some.py arg1 arg2
!pykd.pip -3.9 install --upgrade pykd
!pykd.pip -3.9 list
!pykd.pip -3.9 show pykd

pykd-ext使用细节参看README.md。这就是个安装版Python的Wrapper,没啥特别。

4.1) pykd-ext如何定位Python解释器

pykd-ext通过注册表定位安装版Python,如果你用Python 3.9官方便携版,pykd-ext看不到它。假设winext目录含有便携版Python,如果非要让pykd-ext找到它,可以临时添加注册表项:

reg.exe add "HKCUSOFTWAREPythonPythonCore3.9InstallPath" /v "" /t REG_SZ /d "...\"

pykd-ext只找python39.dll,不找python3.dll、python.exe。

可以修改pykd-ext源码,使之在检查注册表之前优先尝试加载pykd.dll所在目录中的python39.dll,从而直接支持便携版Python。

5) msdia140.dll的幺蛾子

为了使用PDB中的未导出符号,依赖msdia140.dll。有两种方式找到msdia140.dll,一种是向系统注册之,另一种是将之与pykd.pyd置于同一目录。下列命令需要在管理员级cmd中执行:

注册

    regsvr32.exe msdia140.dll

反注册

    regsvr32.exe /u msdia140.dll

若不在管理员级cmd中执行上述命令,会失败报错。用注册法时,msdia140.dll可在任意目录,注册后注册表中有相关项出现。网上有些介绍pykd的文章说得好像非注册msdia140.dll不可,完全不那么回事。不推荐注册法,事实上只要msdia140.dll和pykd.pyd位于同一目录即可,此时无需注册。假设未注册,而msdia140.dll位于"C:WindowsSystem32"目录,这种无效,msdia140.dll必须在pykd.pyd所在目录。

是否注册msdia140.dll,与是否通过pykd-ext使用pykd无关。

若找不到msdia140.dll,也能用pykd,但不能获取PDB中的非导出符号,使得pykd大大受限。测试用例:

"<path>cdb.exe-noinh -snul -hd -o -xe ld:ntdll "C:WindowsSystem32notepad.exe"

.load pykd
!pykd.py
import pykd;hex(pykd.getOffset("ntdll!ZwOpenProcessToken"))

.load pykd.pyd
!pykd.pyd.py
import pykd;hex(pykd.getOffset("ntdll!ZwOpenProcessToken"))

若找不到msdia140.dll,抛异常:

pykd.SymbolException: ZwOpenProcessToken symbol is not found

但"x ntdll!ZwOpenProcessToken"有输出。

msdia140.dll不是OS自带的,pykd自带,VS 2019也有。

14.13.26020.0 (from pykd)

    msdia140.dll

14.26.28619.0

    C:Program Filesdotnetsdk5.0.101TestHostx64msdia140.dll

14.28.29333.0 (from VS 2019)

    C:Program Files (x86)Microsoft Visual Studio2019CommunityDIA SDKbinamd64msdia140.dll
    C:WindowsSystem32msvcp140.dll
    C:WindowsSystem32msvcp140_atomic_wait.dll

前两个只依赖kernel32.dll,第三个还依赖msvcp140.dll、msvcp140_atomic_wait.dll,如果用第三个,必须与msvcp140.dll、msvcp140_atomic_wait.dll放在一起。一般OS有msvcp140.dll,但很可能没有msvcp140_atomic_wait.dll。

☆ pykd vs pykd-ext

无需".load pykd.pyd",可以直接"!pykd.pyd.py"。无需".load pykd",可以直接"!pykd.py"。

不要混用"!pykd.pyd.py"、"!pykd.py",否则要么cdb.exe失去响应,只能杀掉cdb.exe进程,要么cdb.exe直接结束。

"!pykd.pyd.py"找命令行上的some.py时认python39._pth中的路径,可以不指定目录名,只指定文件名。"!pykd.py"找命令行上的some.py时不认python39._pth中的路径。但pykd.pyd处理sys.argv[]的代码有BUG,导致给some.py指定的参数不能稳定传入。

官方推荐通过pykd-ext用pykd。我不推荐pykd-ext,它依赖注册表,不直接支持便携版Python,无端增加系统耦合性。

这两种用法真是各有各的扯淡,后来修改各自源码解决前述问题。

☆ 便携版pykd

windbg、Python、pykd都可以达到便携版效果:

windbg+pykd入门


python39._pth内容示例:

python39.zip
.
Lib/site-packages
pykd

# Uncomment to run site.main() automatically
import site

☆ pykd使用示例

"<path>cdb.exe-noinh -snul -hd -o -xe ld:ntdll "C:WindowsSystem32notepad.exe"

.prompt_allow +reg +ea +dis;rm 0xa
.load pykd.pyd
!pykd.pyd.py
!pykd.pyd.py some.py arg1 arg2
!pykd.pyd.py --local some.py arg1 arg2
!pykd.pyd.py --global some.py arg1 arg2

1) 执行windbg命令

!pykd.pyd.py

这会开一个Python提示符,在其中执行:

import pykd
print( pykd.dbgCommand("r") )

这相当于在windbg提示符中执行"r"。关于dbgCommand()这种API,参[2]。

假设some.py内容如下:

import pykd
import hexdump

buf = pykd.dbgCommand("!teb").encode( 'latin-1' )
hexdump.hexdump( buf )
print( pykd.dbgCommand("!teb") )

!pykd.pyd.py --local some.py

encode()的目的是str转bytes。

2) 读寄存器/内存

import pykd
import hexdump

addr    = pykd.reg("rip")
buf     = bytes( pykd.loadBytes(addr,64) )
hexdump.hexdump( buf )

相当于"db @rip l 0x40"

3) 获取函数地址

import pykd

addr    = pykd.getOffset("KERNEL32!CreateFileW")
print( hex(addr) )

相当于"x KERNEL32!CreateFileW"。

若找不到msdia140.dll,pykd.getOffset("KERNELBASE!CreateFileInternal")抛异
常,找不到符号,因为这是PDB中的未导出符号;而"KERNEL32!CreateFileW"是导出
符号,不依赖msdia140.dll。

4) 条件断点(bp_sample_0.py)

# -*- encoding: cp936 -*-
#
# python3
#
# !pykd.pyd.py --global bp_sample_0.py "KERNELBASE!CreateFileInternal" system.ini 1 1
# !pykd.py -g bp_sample_0.py "KERNELBASE!CreateFileInternal" system.ini 0 0
#

#
# Author : [email protected]
# Create : 2020-12-10 11:51
# Modify :
#

import sys, re, os
import pykd

#
##########################################################################
#

def __PrivateLog ( msg ) :
    sys.stdout.write( msg )
#
# end of __PrivateLog
#

#
##########################################################################
#

def __EndWithPattern ( sth, pattern, ignorecase=False, debug=False ) :
    if ( ignorecase ) :
        s   = sth.lower()
        p   = pattern.lower()
    else :
        s   = sth
        p   = pattern
    if ( s.endswith( p ) ) :
        ret = True
        __PrivateLog( "Hit:[" + sth + "]n" )
    else :
        if ( debug ) :
            __PrivateLog( "[" + sth + "]n" )
        ret = False
    return( ret )
#
# end of __EndWithPattern
#

#
##########################################################################
#

address     = None
pattern     = None
ignorecase  = False
debug       = False
bp          = None

#
# breakpoint handler
#
# By returning False (or nothing) we let the application continue running,
# if you return True then execution will stop.
#
def SomeFunc () :
    rcx = pykd.reg( "rcx" )
    #
    # filename/filepath
    #
    sth = pykd.loadWStr( rcx )
    if ( __EndWithPattern( sth, pattern, ignorecase, debug ) ) :
        returnTrue )
    else :
        returnFalse )
#
# end of SomeFunc
#

def main ( prog, args ) :

    global  address, pattern, ignorecase, debug
    global  bp

    argc        = len( args )
    if ( argc < 2 ) :
        sys.stderr.write    
        (
            'Usage: %s <address> <pattern> [ignorecase] [debug]n'
            %
            prog
        )
        raise SystemExit

    #
    # "KERNELBASE!CreateFileInternal"
    #
    address     = args[0]
    pattern     = args[1]
    if ( argc > 2 ) :
        ignorecase  = int( args[2], 0 )
    if ( argc > 3 ) :
        debug       = int( args[3], 0 )

    bpaddr      = pykd.getOffset( address )
    #
    # 必须持有一个全局引用,避免离开作用域时被析构,否则Ctrl-Break之后g无
    # 法恢复正常状态,断点失效,只有个让你摸不着头脑的提示:
    #
    # 80010117 调用结束后,无法访问调用上下文。
    #
    bp          = pykd.setBp( bpaddr, SomeFunc )
    #
    # continue execution.
    #
    # If you want to break into windbg, you must use Ctrl-Break/Ctrl-Fn-B
    # instead of Ctrl-C
    #
    pykd.go()
#
# end of main
#

if __name__ == '__main__' :
    try :
        main( os.path.basename( sys.argv[0] ), sys.argv[1:] )
    except KeyboardInterrupt :
        pass

这是普通bp断点,要求模块已经加载。

5) 条件断点(bp_sample_1.py)

# -*- encoding: cp936 -*-
#
# python3
#
# !pykd.pyd.py --global bp_sample_1.py KERNELBASE CreateFileInternal system.ini 1 1
# !pykd.py -g bp_sample_1.py KERNELBASE CreateFileInternal system.ini 0 0
#

#
# Author : [email protected]
# Create : 2020-12-10 11:51
# Modify :
#

import sys, re, os
import pykd

#
##########################################################################
#

def PrivateLog ( msg ) :
    sys.stdout.write( msg )
#
# end of PrivateLog
#

#
##########################################################################
#

def EndWithPattern ( sth, pattern, ignorecase=False, debug=False ) :
    if ( ignorecase ) :
        s   = sth.lower()
        p   = pattern.lower()
    else :
        s   = sth
        p   = pattern
    if ( s.endswith( p ) ) :
        ret = True
        PrivateLog( "Hit:[" + sth + "]n" )
    else :
        if ( debug ) :
            PrivateLog( "[" + sth + "]n" )
        ret = False
    return( ret )
#
# end of EndWithPattern
#

#
##########################################################################
#

class PrivateEventHandler ( pykd.eventHandler ) :

    def __init__ ( self, module_name, module_func, pattern, ignorecase=False, debug=False ) :
        #
        # pykd.eventHandler.__init__( self )
        #
        super( PrivateEventHandler, self ).__init__()
        self.module_name    = module_name
        self.module_func    = module_func
        self.pattern        = pattern
        self.ignorecase     = ignorecase
        self.debug          = debug
        self.module_func_bp = None
        pykd.go()
    #
    # end of __init__
    #

    #
    # 相当于bu断点
    #
    def onLoadModule ( self, base, name ) :
        #
        # PrivateLog( "onLoadModule [" + name + "]n" )
        #
        if name == self.module_name :
            module              = pykd.module( name )
            module.reload()
            bpaddr              = module.offset( module_func )
            #
            # self.module_func_bp = pykd.setBp( bpaddr, self.SomeFunc )
            #
            self.module_func_bp = pykd.setBp( bpaddr )
            #
            # PrivateLog( "[Set breakpoint at %#x]n" % bpaddr )
            # PrivateLog( "[Set breakpoint at %#x]n" % self.module_func_bp.getOffset() )
            #
        return( pykd.eventResult.NoChange )
    #
    # end of onLoadModule
    #

    #
    # 本例可以不要该方法
    #
    def onUnloadModule ( self, base, name ) :
        #
        # PrivateLog( "onUnloadModule [" + name + "]n" )
        #
        if name == self.module_name :
            #
            # PrivateLog( "[Remove breakpoint at %#x]n" % self.module_func_bp.getOffset() )
            #
            self.module_func_bp.remove()
            self.module_func_bp = None
        return( pykd.eventResult.NoChange )
    #
    # end of onUnloadModule
    #

    def onBreakpoint ( self, id ) :
        #
        # PrivateLog( "[bpid = %#x]n" % id )
        #
        return( self.SomeFunc() )
    #
    # end of onBreakpoint
    #

    def SomeFunc ( self ) :
        rcx = pykd.reg( "rcx" )
        #
        # filename/filepath
        #
        sth = pykd.loadWStr( rcx )
        if ( EndWithPattern( sth, self.pattern, self.ignorecase, self.debug ) ) :
            returnTrue )
        else :
            returnFalse )
    #
    # end of SomeFunc
    #

#
# end of PrivateEventHandler
#

#
##########################################################################
#

module_name     = None
module_func     = None
pattern         = None
ignorecase      = False
debug           = False
peh             = None

def main ( prog, args ) :

    global  module_name, module_func, pattern, ignorecase, debug
    global  peh

    argc        = len( args )
    if ( argc < 3 ) :
        sys.stderr.write    
        (
            'Usage: %s <module_name> <module_func> <pattern> [ignorecase] [debug]n'
            %
            prog
        )
        raise SystemExit

    module_name = args[0]
    module_func = args[1]
    pattern     = args[2]
    if ( argc > 3 ) :
        ignorecase  = int( args[3], 0 )
    if ( argc > 4 ) :
        debug       = int( args[4], 0 )
    peh         = PrivateEventHandler( module_name, module_func, pattern, ignorecase, debug )
    pykd.go()
#
# end of main
#

if __name__ == '__main__' :
    try :
        main( os.path.basename( sys.argv[0] ), sys.argv[1:] )
    except KeyboardInterrupt :
        pass

这相当于bu断点,允许模块尚未加载。

x) 杂项

一旦pykd.go(),Ctrl-C断不下来,得用Ctrl-Break断。T14笔记本,Ctrl-Fn-B相当于Ctrl-Break。

在.py中设置的断点只在.py中可见,在cdb提示符下不可见。

在.py中设置的断点只能通过.py去删除?有什么办法彻底消除.py的效果吗?

☆ 参考资源

windbg+pykd入门

本文始发于微信公众号(青衣十三楼飞花堂):windbg+pykd入门

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: