我有二进制 A ,这是一个带有附带符号的调试版本 - 很多年前就建立了。我还有二进制 B ,一个没有附带符号的发布版本并且更新近。我正在寻找一种最有效的方法,将二元 A 中的符号与二进制 B 中的潜在候选符号进行匹配。
鉴于调试版本相当大(进行更多输入验证,将更多内容打印到stderr
等)并且函数总是随着时间的推移而变化,我认为尝试指纹各个函数将被浪费时间。
因此,我已经决定 - 非常凭空,所以我可能会咆哮错误的树 - 指纹函数的最佳方法是创建两个二进制文件的调用图并尝试匹配顶点(即功能)。
我已经做了一些预处理,所以我有以下数据结构:
# binary A
[[60, 60, 8734], # function 0 is called by functions 60 (twice) and 8734
[193, 441, 505], # function 1 is called by functions 193, 441 and 505
[193, 742],
[23],
[21],
[21],
[26],
[26, 1508, 1509, 1573],
[24],
[25],
...] # (~10k functions)
# binary B
[[8999], # function 0 is called by function 8999
[9016], # function 1 is called by function 9016
[1126],
[7904, 7904, 7913],
[182, 336, 396, 396],
[9010],
[407],
[182, 632],
[20],
[24],
...] # (~10k functions)
需要注意的一个重要注意事项是,二进制 A 中的函数“0”与二进制 B中的函数“0”之间存在无对应关系。这些是我为每个二进制文件中的每个函数分配的任意ID。
下一步是让我感到困惑的一步。我的算法非常弱,我想不出一个聪明的方法来继续。我(非常有限)的理解是,为了解决这个问题,我想采用某种形式的inexact graph matching。换句话说,哪一组映射Ai-> Bi会最大化两个调用图的相似性吗?
鉴于二进制 A 中还有其他调试功能以及程序随时间演变的明显事实,可能没有完全匹配。理想情况下,我希望输出以下形式:
[[(37, 0.998), (8432, 0.912), (442, 0.75)], # matching-ness of function "0" in binary A with function "37" in binary B is 0.998, second most likely candidate is function "8432" in binary B with score 0.912, etc.
[(42, 0.973), (7751, 0.788)], # matching-ness of function "1" in binary A with function "42" in binary B is 0.973, second most likely candidate is function "7751" in binary B with score 0.788, etc.
[(4579, 0.996), (123, 0.934)],
...] # around ~10k mappings
实际上,如果我只与一位候选人合作并且没有提供排名,我会很高兴,但是人们可以做梦。
任何SO-goers都知道我应该从哪里开始?
答案 0 :(得分:4)
当然是一个有趣的问题,但我怀疑它很难解决。它似乎是有向图上的近似图同构的一个实例。我没有找到太多谷歌搜索,但here's some software解决无向一般图,一个更一般的情况是NP难。
我认为你可以做的最实际的事情是忘记运行时信息,只需获取每个版本的可执行代码部分并使用全局对齐算法(例如Needleman-Wunsch,尽管确实存在对它们的更快但不太准确的算法:
CALL
指令的高权匹配,以及可能的其他“可靠”指令序列。假设函数出现在可执行文件中的顺序没有太大变化(它不会有变化,除非优化版本使用了一些优化,使得链接器放置彼此靠近的函数) ,这应该会给你一个很好的初步近似值。
或者,如果你能找到一种方法(而且我的直觉表明它需要一种迭代的方法,就像PageRank决定网页的价值一样)来“评分”一个函数的可能性{{1调试版本中的函数对应于优化版本中的函数f
,然后是,您可以使用图形匹配技术。在这种情况下,图形的顶点将是两个版本中的所有函数,并且调试版本中的每个函数与优化版本中的每个函数之间都会有加权边缘,权重由您的评分系统确定。
此图表将是二分图,因为在同一版本中,两个函数之间永远不会存在优势。这意味着它是Assignment Problem的一个实例,其中存在相当好(而不是太复杂)的算法。
然而,这里缺少的部分是决定每个配对权重的方法。这样做的一种近似方式是建立一个向量,计算每个函数的直接子,孙子,曾孙等的数量。然后,您可以使用您喜欢的任何距离测量来比较这些矢量。但我希望在这里做这件事不会很好,因为我们已经预计调试版本会包含比优化版本更多的函数调用。
如果您可以访问两者的整个调用树,这将为您提供更多信息:在函数内进行的调用序列,以及对调用的确切层次结构的了解。然后,您可以通过仅使用后者来为每个函数构建“签名”:
现在,Levenshtein distance可用于比较2个签名。为了以更多计算为代价获得更高的精度,您可以使用一种变体,其中允许删除调试版本中最多k个不同的函数,对于一些小k(例如k = 3),并且采用最佳Levenshtein距离在所有这些“瘦身”版本中,附加一个小的惩罚,与删除的功能数量成比例。
答案 1 :(得分:1)