2024年深圳杯数学建模
C题 编译器版本的识别问题
原题再现:
作为一种重要的工具,电子计算机自诞生以来,经历了极为快速的发展。区区百年的时间内,无论从体积、能耗、计算速度,还是应用能力等方面,电子计算机都出现了极为显著的变化。但要充分利用这一工具,必须使用能够被电子计算机解释执行的指令序列,即程序。
最早可用于在电子计算机上执行的程序通常使用机器语言(machine language)编制。由于该类语言并不直观,故它极大地限制了电子计算机的普及。为克服这一困难,1957年诞生了第一个自动编译器,FORTRAN。此后,大量性能更高并支持近乎自然语言的编译器被设计了出来,例如,著名的C/C++编译器,Python编译器等。编译器的出现极大地推动了电子计算机在当代的广泛应用。
为方便使用电子计算机,人们需首先按照一定的规则(即程序设计语言)将需要电子计算机完成的指令以特定的顺序集成在一起,形成脚本(即程序),然后使用编译器自动将脚本翻译为一系列机器语言的组合(即编译),编译器的编译结果最后会提交电子计算机执行。
随着程序设计语言的不断变化,编译器也会不断更新。例如,GCC(the GNU Compiler Collection)就已经更新到了13.2.0版本[1]。不同版本的编译器在编译同一程序脚本时,编译结果会存在一定的差异;相同版本的编译器在使用不同编译选项时,编译结果也会出现差异。能否利用编译结果差异区分编译器的版本?你们的任务是
问题1 使用GCC中不同版本的C++编译器编译附件1中的程序源代码[2],并对比使用默认编译选项时的编译结果。找出区分这些编译结果的主要特征。
问题2 根据问题一中得到的特征,构造一个判别函数,使得能从各版本C++编译器使用默认编译选项时的编译结果,判别区分编译器版本。
问题3 用GCC中不同版本的C++编译器编译附件2中的源程序代码[3],给出直接使用问题2中得到的判别函数区分编译器区版本的结果。研究使用附件1、2原代码编译结果之一都能区分GCC中不同版本的C++编译器的判别函数。
问题4 给出几条提高由编译结果区分编译器版本的判别函数性能的建议,包括区分度和对原代码的泛化性。
整体求解过程概述(摘要)
编译器作为将高级语言转换为机器语言的工具,在软件开发过程中扮演着至关重要的角色。然而,不同版本的编译器可能会引入不同的优化策略、错误修复和安全补丁,这可能导致生成的汇编代码在性能、安全性和兼容性方面存在差异。因此,准确识别编译器版本对于确保软件的质量和安全性具有重要意义。
为了构建有效的机器学习模型,我们首先需要从汇编代码中提取能够反映编译器版本差异的特征。经过深入分析,我们选择了以下关键特征:
操作码频率:不同版本的编译器可能会选择不同的指令序列来实现相同的功能,因此操作码的频率可以作为一个有效的特征。
寄存器使用率:编译器在优化代码时,可能会根据寄存器的可用性和使用效率来分配寄存器。因此,寄存器使用率也可以反映编译器版本的差异。
Bigram数量:Bigram是指相邻的两个操作码或寄存器的组合。通过分析Bigram的数量和分布,我们可以捕捉到编译器在生成代码时的某些模式或偏好。
基于提取的特征,我们构建了Gini决策树模型进行编译器版本的判别。Gini指数是一种衡量数据不纯度的指标,通过递归地选择最优分裂特征和分裂点,我们可以构建一个能够准确识别编译器版本的决策树模型。
虽然Gini决策树模型在一定程度上能够识别编译器版本,但其性能仍存在局限性。为了进一步提升性能,我们引入了XGBoost模型。XGBoost是一种高效的梯度提升算法,通过集成多个弱分类器来构建一个强分类器,具有出色的性能和稳定性。
此外,我们还讨论了以下优化模型的方法:
增加数据集多样性:通过收集更多不同版本编译器的汇编代码样本,我们可以提高模型的泛化能力和准确性。
引入自动特征提取:利用深度学习等技术自动提取汇编代码中的特征,可以减少人工特征工程的复杂性,并可能发现更多潜在的有用特征。
结合静态和动态特征:除了分析汇编代码的静态特征外,我们还可以考虑结合程序的动态执行信息(如执行时间、内存使用情况等)来构建更全面的模型。
编译器版本识别在现实中具有广泛的应用价值。例如,它可以帮助检测和预防编译器相关的安全漏洞,确保软件的安全性;同时,它还可以用于评估和优化编译器的性能,提高软件的运行效率。此外,编译器版本识别还可以为软件逆向工程、恶意软件分析等领域提供有力支持。
本文提出了一种基于机器学习的编译器版本识别解决方案,通过提取汇编代码中的关键特征并构建Gini决策树和XGBoost模型进行判别。我们还讨论了多种优化模型的方法,并指出了编译器版本识别在现实应用中的重要价值。未来,我们将继续深入研究这一问题,探索更多有效的特征提取方法和模型优化策略,以提高编译器版本识别的准确性和效率。
模型假设:
1. 假设不同版本编译器对于实现同一功能,偏向于使用某些特定的操作码与寄存器。因为随着硬件的进步,指令更新,编译器将采用新的指令以实现更快的功能。
2. 假设采用Windows 下的 mingw64 进行编译源代码。因为附件 1 中的源代码含有easyx 图形库,导致gcc 无法在Linux 系统下编译代码。所以我们考虑到既要能够编译,也要使用与gcc相关的编译器,故使用mingw64。
3. 假设不提取汇编文件".ident"的相关特征。无论是gcc还是mingw64采用默认编译出来的汇编文件,都含有".ident"这一项,该项会直接注明汇编文件是由哪个版本的编译器生成的。我们考虑到实际应用中需要判别的是二进制文件,通过二进制文件反编译成汇编文件会失去'.ident' 这个特征。
问题分析:
问题一分析
问题一要求我们提取出不同版本GCC编译附件1结果中的主要特征,以用于构建判别函数。为此,我们首先利用VsCode的文本比较工具,了解各个汇编文件的差异,之后编写程序实现对多个版本编译的汇编文件进行特征提取。我们将考虑提取操作码和寄存器的频率、Bigram(连续两个指令组合)的数量、代码块数量等特征,这些特征能更全面地反映不同版本编译器的行为差异。通过对这些特征的分析,我们为后续的问题二提供了坚实的数据基础。
问题二分析
在问题一的基础上,我们需要利用提取的特征数据构建判别函数。这是一个典型的多分类问题,特征数据将作为输入变量,不同的GCC版本作为标签类别。我们将使用机器学习算法,如Gini决策树、随机森林等,来构建判别模型。通过这些算法,我们能够实现不同版本GCC编译结果的有效分类和识别,并为后续的问题三提供强大的模型支持。
问题三分析
问题三要求利用构建的判别器对不同版本GCC编译附件2的结果进行版本预测。在这个阶段,我们将模型应用于未见过的源代码2生成的汇编文件数据上,以验证其普适性和准确性。考虑到题目要求根据源代码1或2都能构建GCC版本判别函数,我们认为数据规模过小,难以构建具有泛化性的判别函数,所以选择增加数据集规模,以求构建具有高泛化性的判别函数。
问题四分析
为了进一步提升判别函数的性能,我们提出了以下改进建议:首先,可以尝试引入深度学习模型,进行自动特征提取,再基于如Transformer的注意力机制模型,以捕捉更复杂的特征关系;其次,增加数据集的多样性,涵盖不同类型、不同规模的源代码,并通过不同的编译优化级别生成更多样的训练数据;最后,通过结合静态和动态特征,使模型能够从多个维度进行学习和预测,从而提高模型的泛化能力和预测准确率。
模型的建立与求解整体论文缩略图
全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可
程序代码:
python
import os
import re
def process_assembly_file(input_file, output_file):
# 读取原始汇编文件
with open(input_file, 'r', encoding='utf-8') as file:
lines = file.readlines()
# 创建一个新的列表来存储修改后的内容
processed_lines = []
# 遍历原文件的每一行
for line in lines:
# 将逗号替换为空格
line = line.replace(',', ' ')
# 去除每行的前后空格
stripped_line = line.strip()
# 跳过以 . 开头或以 : 结尾的行
if stripped_line.startswith('.') or stripped_line.endswith(':'):
continue
# 按空格分割行内的内容,得到每个单词
words = re.split(r'\s+', stripped_line)
# 处理每个单词
processed_words = []
for word in words:
# 查找是否有括号,并替换为括号内的内容
match = re.search(r'\((%[a-zA-Z0-9]+)\)', word)
if match:
word = match.group(1)
# 处理以 $ 开头的单词
if word.startswith('$'):
if re.match(r'\$\d+', word): # 如果 $ 后跟数字
word = '$0'
elif re.match(r'\$-', word): # 如果 $ 后跟负号
word = '$0'
elif word.startswith('$.'): # 如果 $ 后跟 .
word = '$.'
elif word.startswith('$g'): # 如果 $ 后跟 g
word = '$g'
processed_words.append(word)
# 将处理后的单词重新组合成一行
processed_line = ' '.join(processed_words)
processed_lines.append(processed_line + '\n') # 保持行的格式
# 写入新的汇编文件
with open(output_file, 'w', encoding='utf-8') as file:
file.writelines(processed_lines)
def process_all_files(input_folder, output_folder):
# 如果输出文件夹不存在,则创建
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 遍历输入文件夹中的所有文件
for filename in os.listdir(input_folder):
# 构造完整的文件路径
input_file = os.path.join(input_folder, filename)
output_file = os.path.join(output_folder, filename)
# 处理每个文件
process_assembly_file(input_file, output_file)
input_folders = ['8.4.0', '10.2.0', '11.3.0', '12.2.0', '13.2.0']
output_folders = [f"{folder}_processed" for folder in input_folders]
print(output_folders)
# 处理所有文件
for input_folder, output_folder in zip(input_folders, output_folders):
process_all_files(input_folder, output_folder)
print("所有文件处理完毕。")
python
import csv
import os
from collections import Counter
file_count = 0 # 初始化文件计数器
def process_folder(folder_name):
data = []
global file_count
file_count += 1 # 每处理一个文件夹,计数器加一
for filename in os.listdir(folder_name):
print(f"Processing {filename}...")
if 'test' not in filename:
print(f"Skipping {filename}...")
continue
file_path = os.path.join(folder_name, filename)
with open(file_path, 'r') as file:
word_counter = Counter()
bigram_counter = Counter()
first_word_list = []
for line in file:
words = line.strip().split()
if not words:
continue
first_word_list.append(words[0])
word_counter.update(words)
bigram_counter.update(zip(words[:-1], words[1:]))
# 计算四元组(四个连续单词)的频率
quadgram_counter = Counter(
zip(first_word_list[:-3], first_word_list[1:-2],
first_word_list[2:-1], first_word_list[3:]))
# 筛选频率大于10的特征
word_features = {
word: count
for word, count in word_counter.items() if count > 10
}
bigram_features = {
" ".join(bigram): count
for bigram, count in bigram_counter.items() if count > 10
}
quadgram_features = {
" ".join(quadgram): count
for quadgram, count in quadgram_counter.items() if count > 10
}
# 构建一行数据
features = {
**word_features,
**bigram_features,
**quadgram_features
}
features['label'] = file_count # 标签为当前文件的编号
data.append(features)
return data
def save_to_csv(data, output_file):
keys = set()
for row in data:
keys.update(row.keys())
keys = sorted(keys) # 按字母排序,确保一致性
with open(output_file, 'w', newline='') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=keys)
writer.writeheader()
for row in data:
writer.writerow(row)
# 定义已处理的文件夹列表
processed_folders = [
'8.4.0_processed', '10.2.0_processed', '11.3.0_processed',
'12.2.0_processed', '13.2.0_processed'
]
# 初始化用于保存所有文件夹数据的列表
all_data = []
# 遍历每个文件夹并处理数据
for folder in processed_folders:
folder_data = process_folder(folder)
all_data.extend(folder_data)
# 保存合并后的数据到CSV文件
save_to_csv(all_data, 'no_our_data.csv')