使用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

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali

C#中lock关键字的使用小结

《C#中lock关键字的使用小结》在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时,其他线程无法访问同一实例的该代码块,下面就来介绍一下lock关键字的使用... 目录使用方式工作原理注意事项示例代码为什么不能lock值类型在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时