标题: 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
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
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
/ * 该函数用于定位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
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位于:
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大 大受限。测试用例:
"
.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使用示例
"
.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