使用WinDBG调试OnDO Server

2023-10-30 23:10
文章标签 使用 调试 server windbg ondo

本文主要是介绍使用WinDBG调试OnDO Server,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、问题

在OnDo服务端和PJSIP客户端配置好IPv6,发现电话机可以向服务器注册成功,但使用话机A拨打话机B时,OnDo服务器返回500 (Server Internal Error) 错误。抓的包如下:

 

通过仔细对比与IPv4下INVITE的请求,没有发现明显差异。服务器的嫌疑比较大。

二、分析

要分析这个问题,首先需要定位服务器是何时发送500错误的,为此需要跟踪服务器的执行过程。

在这里我们使用WinDBG来调试跟踪,WinDBG是微软提供的在Windows上强大的调试工具,下载页面在这里:
http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx
如果页面过期,可以直接在MSDN中搜索 WinDBG 即可找到相关的页面。基本的调试命令这里也不解释了,文档非常详细,网络上相关的资料也非常多。

运行WinDBG,点击 File -> Attach to process ,我们要附加到OnDo Server的进程,然后才能进行调试。那么怎么找到这个进程?如果你对OnDo Server非常熟悉,知道它是一个运行于Java平台上的一个组件,那么你会非常容易找到那个合适的java.exe进程。如果不熟悉的话,我们可以这样做:
首先,打开Windows的控制台,输入这个命令:

wmic process list full

 

这个命令会详细列出正在运行的每个进程的详细信息,仔细找里面的一个(可以重定向到一个文件中,搜索ondo即可)

CommandLine="C:\Program Files\Java\jre7\bin\java" ... com.brekeke.ondo.sv ...
Description=java.exe
Handle=2368
Name=java.exe
...

在这里省略了一些大部分信息,只列出了其中几个属性,但对我们已经足够了。从CommandLine属性,我们知道我们没有看错人(进程);现在只需去找PID为2368,名称为java.exe的进程就可以了。

使用WinDBG附加到这个进程上。

然后,我们要设置断点,理想的断点是当OnDo产生500错误的时候(一定有会一个判断语句,从这个语句进入了这个分支),但这似乎并不现实,如果我们知道它如何产生的500错误,就不用如此费力的分析原因了。

所以,我们可以找到它发送500错误的时候。我们知道,在Windows平台上,网络通信最终使用的基本都是WinSock——当然也可以不是,但这种情况并不多见,我们先做这样的假设,OnDo使用的是WinSock,如果后面进行不下去了,我们再回头来分析其他情况,不过谢天谢地,这件事没有发生,我们的假设是正确的。使用WinSock,发送消息的函数并不多,就四个,下面列出了四个函数的声明:

int send(_In_  SOCKET s,_In_  const char *buf,_In_  int len,_In_  int flags
);int sendto(_In_  SOCKET s,_In_  const char *buf,_In_  int len,_In_  int flags,_In_  const struct sockaddr *to,_In_  int tolen
);int WSASend(...);    // 参数省略,详见MSDNint WSASendTo(...);  // 参数省略,详见MSDN

 更详细的信息可以查阅MSDN,WinSock Functions:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms741394%28v=vs.85%29.aspx
从MSDN我们还可以知道,这些函数都在ws2_32.dll中,因此我们就可以很方便的设置断点了(实际上我只设置了两个,而只用到了一个):

0:018> bp ws2_32!send
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\system32\WS2_32.dll -
0:018> bp ws2_32!sendto
0:018> bl0 e 71a24c27     0001 (0001)  0:**** WS2_32!send1 e 71a22f51     0001 (0001)  0:**** WS2_32!sendto

粗体部分就是我使用的命令。

现在我们可以打电话了。我们设置的断点,会截获所有调用send和sendto的地方,因此在调试过程中,我们可以看到,OnDo首先给客户端回复100 Trying 消息,然后尝试转发INVITE消息(并失败),然后再给客户端回复500 Server Internal Error 消息。这个过程很清晰,我们可以很明确的看到,转发INVITE消息的sendto调用,返回值是-1 (0xFFFFFFFF),也就是SOCKET_ERROR。

这个事情让人很疑惑,我的两个话机是在同一个ONT上的,IP地址是一样的,唯一不同的就是注册的号码和绑定的端口号,sendto显然不会涉及到注册的号码,那么为什么给同一个IP地址的两个端口发送数据会导致两种截然不同的结果呢?我们还记得两部话机的注册都是好的,500错误也可以收到,那么是什么导致这样的差异呢?对此,我们可以详细比较一下转发INVITE和发送100通知(或者500错误)的两次sendto调用的参数。

下图是发送100 Trying 消息时调用 sendto() 的堆栈(从esp寄存器看就可以了)。

 

下图是转发INVITE 消息时调用 sendto() 的堆栈。

 

这样看还不够明显。我们知道 sendto() 的 6 个参数,因此逐个对比一下:

参数

100 Trying

INVITE

Socket

60 0d 00 00

60 0d 00 00

Buffer

44 f1 54 03

44 f1 64 03

Buffer length

81 01 00 00

89 04 00 00

Flags

00 00 00 00

00 00 00 00

Socket address

28 f1 54 03

28 f1 64 03

Socket address length

1c 00 00 00

1c 00 00 00

第二、三、四个参数基本可以忽略。我们看到,两次发送,连套接字使用的都是相同的。似乎INVITE更加没有理由发送失败了。现在唯一不同就是发送的目的地 (struct sockaddr 结构) 了,我们再到那块内存中继续寻找线索。

这个内存在截图中已经体现出来了(就是在发送的buffer前面的28个字节)。这个结构的定义是这样的:

struct in6_addr {union {u_char  Byte[16];u_short Word[8];} u;
};struct sockaddr_in6 {short   sin6_family;         // 2u_short sin6_port;           // 2u_long  sin6_flowinfo;       // 4struct  in6_addr sin6_addr; // 16u_long  sin6_scope_id;       // 4   
};

应该明确一下,IPv4和IPv6使用的结构是不同的,这也是sendto() 最后一个参数的用途,我们这里自然只需关心IPv6的结构。为了更加明显的进行对比,请看下表:

成员

100 Trying

INVITE

sin6_family

17 00

17 00

sin6_port

13 d8

13 da

sin6_flowinfo

00 00 00 00

00 00 00 00

sin6_addr

fe c0 00 00 00 00 00 00

c3 0c ab ff 13 6f 22 1c

fe c0 00 00 00 00 00 00

c3 0c ab ff 13 6f 22 1c

sin6_scope_id

01 00 00 00

00 00 00 00

其中差异已经用红蓝两种颜色表示出来了。前面我说过,我的两个话机是在同一个ONT上,使用不同的端口注册的,所以他们共享同一个IP。从这里你可以明确的看出两个端口分别是5080(0x13d8)和5082(0x13da),因此这个差异是预料之中的。那么剩下的就只有 sin6_scope_id 了。为什么一个是1,另一个是0呢?其实首先应该问,是这个差异导致的 sendto() 失败进而服务器返回 500 错误吗?答案是肯定的。当我们人为的把这个0改为1的时候,奇迹(其实不是奇迹,而是我们预期的现象)出现了,INVITE发送成功,另一个话机响铃了。现在要问,为什么一个是1,另一个是0呢?我不知道。是的,我不知道。同样的配置,在Windows 7 系统上完全没有问题,而在 Windows XP 上就戏剧性的出现了上面的一幕。在OnDO的配置上,Configuration -> System -> Network,我们只设置了IPv6地址,而没有设置 scope 。

 

因此,OnDO在打算从这个 interface 发送 INVITE 请求时,使用了某种方法获取 scope ID,而显然它没有获取到(或者获取到了错误的值),后面的事情我们都知道了。因此,假如我们在这里明确指定 scope :

 

结果就正确了,所有的数据包都正常发送,电话可以通了。友情提示,这里的 scope 可以在命令行输入 ipconfig 查看,请不要猜测。

到这里,问题已经解决了。现在我依然很诧异OnDO在Windows XP和Windows 7系统的表现上的差异究竟来源于何处。

三、结论

归根结底,还是OnDO 服务器的配置问题,至少在 Windows XP 系统上,如果使用IPv6,需要把 scope 同时填写到地址上。这一点在Brekeke官方的Wiki上并没有体现(或许是他隐含的意思?),这就导致了我们兜了一个大圈。

转载于:https://www.cnblogs.com/zhangbaoqiang/p/3145326.html

这篇关于使用WinDBG调试OnDO Server的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/311166

相关文章

Java Lambda表达式的使用详解

《JavaLambda表达式的使用详解》:本文主要介绍JavaLambda表达式的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言二、Lambda表达式概述1. 什么是Lambda表达式?三、Lambda表达式的语法规则1. 无参数的Lambda表

Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析

《Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析》InstantiationAwareBeanPostProcessor是Spring... 目录一、什么是InstantiationAwareBeanPostProcessor?二、核心方法解

IDEA如何实现远程断点调试jar包

《IDEA如何实现远程断点调试jar包》:本文主要介绍IDEA如何实现远程断点调试jar包的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录问题步骤总结问题以jar包的形式运行Spring Boot项目时报错,但是在IDEA开发环境javascript下编译

详解如何使用Python构建从数据到文档的自动化工作流

《详解如何使用Python构建从数据到文档的自动化工作流》这篇文章将通过真实工作场景拆解,为大家展示如何用Python构建自动化工作流,让工具代替人力完成这些数字苦力活,感兴趣的小伙伴可以跟随小编一起... 目录一、Excel处理:从数据搬运工到智能分析师二、PDF处理:文档工厂的智能生产线三、邮件自动化:

Spring @RequestMapping 注解及使用技巧详解

《Spring@RequestMapping注解及使用技巧详解》@RequestMapping是SpringMVC中定义请求映射规则的核心注解,用于将HTTP请求映射到Controller处理方法... 目录一、核心作用二、关键参数说明三、快捷组合注解四、动态路径参数(@PathVariable)五、匹配请

Java 枚举的基本使用方法及实际使用场景

《Java枚举的基本使用方法及实际使用场景》枚举是Java中一种特殊的类,用于定义一组固定的常量,枚举类型提供了更好的类型安全性和可读性,适用于需要定义一组有限且固定的值的场景,本文给大家介绍Jav... 目录一、什么是枚举?二、枚举的基本使用方法定义枚举三、实际使用场景代替常量状态机四、更多用法1.实现接

springboot项目中使用JOSN解析库的方法

《springboot项目中使用JOSN解析库的方法》JSON,全程是JavaScriptObjectNotation,是一种轻量级的数据交换格式,本文给大家介绍springboot项目中使用JOSN... 目录一、jsON解析简介二、Spring Boot项目中使用JSON解析1、pom.XML文件引入依

Java中的record使用详解

《Java中的record使用详解》record是Java14引入的一种新语法(在Java16中成为正式功能),用于定义不可变的数据类,这篇文章给大家介绍Java中的record相关知识,感兴趣的朋友... 目录1. 什么是 record?2. 基本语法3. record 的核心特性4. 使用场景5. 自定

Python使用Tkinter打造一个完整的桌面应用

《Python使用Tkinter打造一个完整的桌面应用》在Python生态中,Tkinter就像一把瑞士军刀,它没有花哨的特效,却能快速搭建出实用的图形界面,作为Python自带的标准库,无需安装即可... 目录一、界面搭建:像搭积木一样组合控件二、菜单系统:给应用装上“控制中枢”三、事件驱动:让界面“活”

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示