z/OS平台PL/I远程调试VSCode调试扩展开发方案咨询
z/OS PL/I VSCode远程调试扩展实现指南
1. VSCode调试能力初始化
VSCode调试能力基于Debug Adapter Protocol(DAP)实现,不需要自行开发调试UI,所有界面渲染由VSCode内置完成,初始化只需要完成3项注册工作:
- 在扩展
package.json中声明自定义调试类型,定义调试启动所需的配置项,包括z/OS主机地址、调试端口、源数据集名、作业名等参数,同时关联PL/I语言,让PL/I文件的调试菜单自动识别该调试类型 - 实现
DebugConfigurationProvider,负责处理launch.json的配置补全、默认值填充、参数合法性校验,比如提前校验用户填写的数据集路径格式、作业参数是否缺失 - 实现
DebugAdapterDescriptorFactory,负责创建调试适配器实例:如果TCP通信逻辑量不大,可以直接用内联模式在扩展宿主进程里跑适配逻辑;如果通信逻辑复杂,建议单独启动独立适配进程跑TCP通信,避免阻塞扩展主线程
最小配置注册示例:
{ "contributes": { "debuggers": [ { "type": "zos-pli-remote", "label": "z/OS PL/I 远程调试", "languages": ["pli"], "configurationAttributes": { "launch": { "required": ["host", "debugPort", "sourceDataset", "jobName"], "properties": { "host": {"type": "string", "description": "z/OS主机IP/域名"}, "debugPort": {"type": "number", "description": "大型机侧调试服务端口"}, "sourceDataset": {"type": "string", "description": "PL/I源数据集路径"}, "jobName": {"type": "string", "description": "待提交调试的作业名"} } } } } ] } }
扩展激活阶段的注册代码示例(TypeScript):
import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext) { // 注册调试配置提供器 const configProvider = new PLIDebugConfigProvider(); context.subscriptions.push( vscode.debug.registerDebugConfigurationProvider('zos-pli-remote', configProvider) ); // 注册调试适配器工厂 const adapterFactory = new PLIDebugAdapterFactory(); context.subscriptions.push( vscode.debug.registerDebugAdapterDescriptorFactory('zos-pli-remote', adapterFactory) ); }
2. TCP调试启动指令发送
这部分可以直接复用你已经通过抓包得到的IBM z/OS Explorer通信报文格式,结合现有扩展已经实现的z/OS操作能力完成:
- 调试会话触发后,优先复用现有能力完成数据集写入、作业提交两个前置步骤,不需要重复开发这部分逻辑
- 作业提交后增加端口轮询逻辑,等大型机侧作业初始化完成、调试端口进入监听状态后再建立TCP长连接,避免连接被拒
- 注意z/OS侧默认使用IBM-1047(EBCDIC)编码,所有发送的文本类报文需要做UTF-8到EBCDIC的编码转换,不要直接发送UTF-8编码内容,否则会出现指令解析失败
- 启动指令发送后增加10-15秒的超时校验,超时未收到就绪响应直接终止会话,返回明确的错误提示,比如“调试作业启动超时,请检查大型机侧JES日志”
TCP连接与启动指令发送示例:
import * as net from 'net'; import * as iconv from 'iconv-lite'; const client = new net.Socket(); // 轮询端口就绪后再连接 waitForPortReady(host, debugPort, 15000).then(() => { client.connect(debugPort, host, () => { // 按抓包得到的报文结构拼装启动指令,替换对应作业、用户标识字段 const startCmdRaw = buildStartCommand(jobName, userToken); // 编码转换后发送 const startCmdBuf = iconv.encode(startCmdRaw, 'IBM1047'); client.write(startCmdBuf); }); });
3. TCP返回信息接收与界面渲染
核心逻辑是将大型机返回的原始调试报文转换为标准DAP事件推送给VSCode,所有界面渲染由VSCode自动完成:
- TCP数据接收时必须做拆包粘包处理:z/OS侧的调试报文一般采用固定4字节长度头+报文体的结构,不要直接按单次
data事件触发就解析报文,避免拿到半截数据解析失败,需要维护接收缓冲区,按报文长度切割完整报文后再处理 - 解析得到的调试事件(命中断点、程序输出、变量值、调用栈)对应转换为DAP标准事件结构发送给VSCode
- 提前做好路径映射:大型机侧返回的源位置是「数据集+成员名」格式,需要映射为本地工作区对应的PL/I文件路径,否则VSCode无法正确打开源文件、显示断点命中位置
- 普通程序运行输出转换为DAP的
output事件发送,会自动展示在VSCode调试控制台
报文接收与事件推送示例:
let recvBuffer = Buffer.alloc(0); client.on('data', (chunk) => { recvBuffer = Buffer.concat([recvBuffer, chunk]); // 按固定长度头拆包 while (recvBuffer.length >= 4) { const msgLen = recvBuffer.readUInt32BE(0); if (recvBuffer.length < msgLen + 4) break; const rawMsg = recvBuffer.subarray(4, 4 + msgLen); recvBuffer = recvBuffer.subarray(4 + msgLen); // 解码并解析原始z/OS调试报文 const msgText = iconv.decode(rawMsg, 'IBM1047'); const debugEvent = parseZOSDebugMessage(msgText); // 转换为DAP事件推送给VSCode switch(debugEvent.type) { case 'stopped': session.sendEvent(new StoppedEvent('breakpoint', debugEvent.threadId)); break; case 'output': session.sendEvent(new OutputEvent(debugEvent.content + '\n', 'stdout')); break; case 'initialized': session.sendEvent(new InitializedEvent()); break; } } });
4. 核心调试交互功能实现
所有调试交互都是双向请求响应模式:VSCode将用户操作转换为DAP请求发给调试适配器,适配器将请求转换为对应TCP报文发给大型机,拿到响应后再转回DAP格式回传给VSCode即可,核心需要实现4类请求处理:
- 执行控制:处理
continue(继续)、next(单步跳过)、stepIn(单步进入)、stepOut(单步跳出)请求,按抓包得到的报文格式拼装对应控制指令发给大型机,收到线程暂停响应后回传DAP响应,VSCode会自动刷新调用栈、变量面板 - 会话终止:处理
disconnect(停止调试)请求,先给大型机发送调试退出指令,主动关闭TCP连接,再调用现有扩展的作业取消能力清理大型机侧残留进程,最后回传断开响应 - 断点管理:处理
setBreakpoints(行断点)、setExceptionBreakpoints(异常断点)请求,将VSCode传入的本地文件路径、行号转换为大型机识别的「数据集+成员+行号」格式,发送断点设置指令,将大型机返回的断点生效结果回传给VSCode,VSCode会自动将未生效的断点置灰 - 信息查询:处理
stackTrace(调用栈)、scopes(变量作用域)、variables(变量值)请求,发送对应查询指令给大型机,将返回结果转换为DAP格式回传,VSCode会自动渲染到左侧调试面板
断点设置请求处理示例:
protected async setBreakPointsRequest( response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments ): Promise<void> { const localPath = args.source.path; // 本地路径转z/OS数据集名 const datasetName = mapLocalPathToDataset(localPath); const reqBreakpoints = args.breakpoints || []; const verifiedBps = []; for (const bp of reqBreakpoints) { // 发送断点设置指令到大型机 const result = await sendSetBreakpointCmd(client, datasetName, bp.line); verifiedBps.push({ line: bp.line, verified: result.success, id: result.bpId }); } response.body = { breakpoints: verifiedBps }; this.sendResponse(response); }
开发注意事项
- 不需要从头实现DAP协议的序列化、基础通信逻辑,直接使用官方维护的
@vscode/debugadapter依赖包,里面已经封装了所有标准DAP类型和基础处理逻辑,只需要重写对应请求的处理方法即可 - 联调阶段可以先在本地写一个简单的TCP mock服务,模拟大型机侧的调试响应,先把DAP交互流程跑通,再对接真实大型机环境,减少联调成本
- 所有涉及z/OS文本内容的收发都要做编码校验,避免因为编码不兼容导致指令失败、乱码问题
内容的提问来源于stack exchange,提问作者Klaus Raab
相关产品推荐
相关产品推荐

