有时开发一个Windows应用层程序,用Windbg调试,要进行源码中的断点调试,总需要如下繁复的操作:
- 查看进程管理器管理器,记下要调试的进程的id。
!process <进程id> 0
,获取进程的EProcess
.process /p /i <进程eprocess>
,进入目标进程的上下文。.reload
,加载调试符号。- 打开源文件,设置断点。
F5
继续运行,触发断点。
为了节省打命令的时间,于是想写一个脚本,只要把目标进程的id提供给它,由它完成以上工作。先看一下!process <进程id> 0
命令打印的结果:
思路就是遍历这个打印的结果中的每个单词,遇到PROCESS
,则在循环的下一轮中可取到进程的EProcess
值,然后用这个EProcess
值切换到目标进程的上下文。完整的脚本如下:
ad /q ${/v:procInfo};
ad /q ${/v:eprocess};
ad /q ${/v:procName};
aS /c ${/v:procInfo} "!process $arg1 0";
.block {
.foreach /s (token "${procInfo}") {
.if ('${token}' == 'PROCESS') {
aS ${/v:eprocess} b;
.echo <z: found word `PROCESS`>;
.continue;
}
.if ('${eprocess}' == 'b') {
aS ${/v:eprocess} token;
.echo <z: found eprocess>: token;
.continue;
}
.if ('${token}' == 'Image:') {
aS ${/v:procName} b;
.echo <z: found word `Image:`>;
.continue;
}
.if ('${procName}' == 'b') {
aS ${/v:procName} token;
.echo <z: found procName>: token;
.break;
}
}
.block {
.if ('`${/n:eprocess}`' == '`eprocess`') {
.echo eprocess;
.process /p /i ${eprocess};
g;
} .else {
.echo <z: Cannot found process $arg1 >;
}
}
}
有两点要注意:
-
在脚本的开头用了
ad
命令将后续用到的几个别名作一次清除,以防上次使用脚本后别名的残留值影响到本次运行。 -
使用
.block {}
块,是为了防止脚本一运行便将.if ('${eprocess}' == 'b')
中的${eprocess}
展开。如果把.block {}
去掉,就会发现${eprocess}
一直都是eprocess
。具体原因可参考这篇。 -
20230919修改: 将循环后的对
eprocess
的判断语句改为:.if (‘`${/n:eprocess}`’ == ‘`eprocess`’)
测试过对
${/d:eprocess}
进行判断(若eprocess
已定义,则展开为1,否则展开为0),发现该值无论如何都展开为0,故选用${/n:eprocess}
(若eprocess
已定义,则展开为eprocess
,否则展开为${/n:eprocess}
)。另外,不能直接写成:.if (‘${/n:eprocess}’ == ‘eprocess’)
因为
'eprocess'
会以eprocess
的实际数值展开,比如变成'ffffa8089eadf300'
这样的。而写成 ‘`eprocess`’ ,即用反引号再加单引号包起来,就不会被展开(不能仅用单引号包起来,会有语法错误)。
使用时,直接执行$$>a<D:\zbh\myScripts\myWindbgScripts\myAttachProc.wds <进程id>
。因为使用.process
命令时传入了/i
参数,即进行侵入式调试,会导致windbg在下面的g
命令后停下来,这时脚本中剩下的命令不再执行,所以最后要手动执行一次.reload
命令,加载调试符号。如下是运行脚本的结果,之后再手动.reload
即可: