Skip to content

标题: windbg+pykd入门

创建: 2020-12-10 11:51 更新: 2020-12-23 11:51 链接: https://scz.617.cn/python/202012101151.txt


目录:

☆ pykd简介
☆ 编译pykd源码
    9) 修正pykd.pyd处理sys.argv[]的BUG
☆ 编译pykd-ext源码
    2) 修改pykd-ext使之支持从pykd子目录加载.py
☆ 安装pykd
    1) pip获取pykd.pyd
    2) 从pypi.org下载
    3) 从githomelab.ru下载(官方发布)
    4) pykd-ext
        4.1) pykd-ext如何定位Python解释器
    5) msdia140.dll的幺蛾子
☆ pykd vs pykd-ext
☆ 便携版pykd
☆ pykd使用示例
    1) 执行windbg命令
    2) 读寄存器/内存
    3) 获取函数地址
    4) 条件断点(bp_sample_0.py)
    5) 条件断点(bp_sample_1.py)
    x) 杂项
☆ 参考资源

☆ pykd简介

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

先统一一下术语:


常用称呼 对应文件 windbg加载方式 必需 支持便携版Python

pykd pykd.pyd .load pykd.pyd Y Y pykd-ext pykd.dll .load pykd N N


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

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"

参看:

pykd\pykd\windbgext.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(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-ext\packages\ pykd-ext\packages\boost.1.67.0.0\

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

pykd-ext\out\x64\Release\pykd.dll pykd-ext\out\x64\Release\pykd_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中的路径,参看:

pykd\pykd\windbgext.h pykd\pykd\windbgext.cpp


void PykdExt::setUp() { ... Py_Initialize(); ... python::object sys = python::import("sys"); ... / * 取sys.path / python::list pathList = python::extract(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(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::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-ext\sources\windbgext.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();
    fprintf( stderr, "GetModuleHandleEx() failed, error = %d\n", ret );
    return( -1 );
}
if ( 0 == GetModuleFileNameA( hm, path, pathlen ) )
{
    int ret = GetLastError();
    fprintf( stderr, "GetModuleFileName() failed, error = %d\n", ret );
    return( -1 );
}
char   *p   = strrchr( path, '\\' );
if ( p != NULL )
{
    *p  = '\0';
}
return( 0 );

} / end of PrivateGetCurrentModulePath /

/ * added by scz / std::string getScriptFileName2(const std::string &scriptName) { char path[MAX_PATH];

if ( 0 == PrivateGetCurrentModulePath( path, sizeof( path ) ) )
{
    // printf( "%s\n", 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( "%s\n", 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位于:

\Lib\site-packages\pykd\pykd.pyd

2) 从pypi.org下载

https://pypi.org/project/pykd/#files https://files.pythonhosted.org/packages/12/2d/fabb94c8bdbfc1748da0f21867ed44eb12a6b016bfe87abe5872ba75d6a3/pykd-0.3.4.15-cp39-none-win_amd64.whl

这里有一堆.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 https://githomelab.ru/pykd/pykd-ext/-/wikis/Downloads https://githomelab.ru/pykd/pykd-ext/-/wikis/uploads/0bc82100609d24a8fcd1604203e782a6/pykd_ext_2.0.0.24.zip https://githomelab.ru/pykd/pykd-ext/-/blob/master/README.md

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 "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /v "" /t REG_SZ /d "...\" reg.exe query "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /v "" reg.exe delete "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /v "" /f reg.exe delete "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /f reg.exe delete "HKCU\SOFTWARE\Python" /f

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:\Windows\System32"目录,这种无效,msdia140.dll必须在pykd.pyd所在目录。

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

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

"\cdb.exe" -noinh -snul -hd -o -xe ld:ntdll "C:\Windows\System32\notepad.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 Files\dotnet\sdk\5.0.101\TestHost\x64\msdia140.dll

14.28.29333.0 (from VS 2019)

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\DIA SDK\bin\amd64\msdia140.dll
C:\Windows\System32\msvcp140.dll
C:\Windows\System32\msvcp140_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都可以达到便携版效果:

winext | | pykd.dll // 无所谓 | pykd_ext_2.0.pdb // 不需要 | pykd.pdb // 不需要 | pykd.pyd // 必需 | msdia140.dll // 建议 | msvcp140.dll // 建议 | msvcp140_atomic_wait.dll // 建议 | python39.dll | python39.zip | python39._pth | +---Lib | ---site-packages | hexdump.py | ---pykd sys_argv.py // 自己写的脚本

python39._pth内容示例:


python39.zip . Lib/site-packages pykd

Uncomment to run site.main() automatically

import site

☆ pykd使用示例

"\cdb.exe" -noinh -snul -hd -o -xe ld:ntdll "C:\Windows\System32\notepad.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 : scz@nsfocus

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 ) ) : return( True ) else : return( False )

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 : scz@nsfocus

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 ) ) :
        return( True )
    else :
        return( False )
#
# 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的效果吗?

☆ 参考资源

[1] PyKD Tutorial - Sina Karvandi [2018-05-25] https://rayanfam.com/topics/pykd-tutorial-part1/ https://rayanfam.com/topics/pykd-tutorial-part2/

[2] pykd API Reference https://githomelab.ru/pykd/pykd/-/wikis/API%20Reference

pydk User Manual (俄文版)
https://githomelab.ru/pykd/pykd/-/wikis/User%20Manual%20rus