☆ pykd简介
传统windbg插件是用C开发的,jsprovider.dll允许用JavaScript写windbg插件,pykd.pyd则允许用Python写windbg插件,pykd同时支持Python 2.x/3.x。
先统一一下术语:
本文测试用例所涉及的组件如下:
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<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-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::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-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位于:
<Python>\Lib\site-packages\pykd\pykd.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 "HKCU\SOFTWARE\Python\PythonCore\3.9\InstallPath" /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:\Windows\System32"目录,这种无效,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:\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都可以达到便携版效果:
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:\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的效果吗?
☆ 参考资源