diff --git a/src/abi-info-check.py b/src/abi-info-check.py index 94971897315ebe54f6e1573d406697b97407c55f..7bf35bb050ee041e504f5b72056140246e5bfc97 100644 --- a/src/abi-info-check.py +++ b/src/abi-info-check.py @@ -9,14 +9,12 @@ import re import subprocess import sys import tarfile -import xml.etree.ElementTree as ET import distro -import pandas as pd +from utils import * TOOL_VERSION = "1.0" - ABI_CC = "abi-compliance-checker" ABI_DUMPER = "abi-dumper" @@ -25,31 +23,11 @@ ERROR_CODE = {"Ok": 0, "Error": 1, "Empty": 10, "NoDebug": 11, "NoABI": 12} ARGS = {} - -def run_cmd(cmd): - ''' - run command in subprocess and return exit code, output, error. - ''' - os.putenv('LANG', 'C') - os.putenv('LC_ALL', 'C') - os.environ['LANG'] = 'C' - os.environ['LC_ALL'] = 'C' - cmd = cmd.encode('UTF-8') - proc = subprocess.Popen(cmd, shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - (stdout, stderr) = proc.communicate() - returncode = proc.returncode - return (returncode, stdout, stderr) - - def detect_os(): if distro.id != 'uos': logging.error('please run in UOS') exit() - def parse_args(): desc = "Analyze abi infomation about bin file." parser = argparse.ArgumentParser( @@ -78,158 +56,12 @@ def extract_tarball(tarball, export_dir): def get_rpmname_without_libs(x): # 正则获取rpmname return re.sub('-libs$', '', x).strip() - - -def find_devel_lib_package(export_dir): - print('Checking the dependency ...') - file = f'{export_dir}/OLD-pkgs.info' - if not os.access(f'{file}', os.R_OK): - exit_status( - "Error", f"The info file {file} can't be read. Please check") - - dev_pkgs_set = set() - libs_pkgs_set = set() - dbginfo_pkgs_set = set() - with open(file, 'r') as f: - for line in f: - rpm_name = get_rpmname_without_libs(line) - dev_pkgs_set.add(f'{rpm_name}-devel') - dbginfo_pkgs_set.add(f'{rpm_name}-debuginfo') - # 包名中包含 lib 关键字的包,本身就是 lib 包 - if 'lib' not in rpm_name: - libs_pkgs_set.add(f'{rpm_name}-libs') - else: - libs_pkgs_set.add(f'{rpm_name}') - return dev_pkgs_set, libs_pkgs_set, dbginfo_pkgs_set - - -def try_install_packages(dev_pkgs, libs_pkgs=set()): - not_installed_pkgs = set() - - dev_pkgs = set(dev_pkgs) | set(libs_pkgs) - for pkg in dev_pkgs: - cmd = f'rpm -q {pkg}' - returncode, stdout, stderr = run_cmd(cmd) - if returncode != 0: - print(f"It seems that the OS doesn't install {pkg}") - not_installed_pkgs.add(pkg) - else: - print(f'The packages "{pkg}" have been installed') - - install_failed_pkgs = set() - for pkg in not_installed_pkgs: - cmd = f'yum install -y {pkg}' - print(f'Trying to install package {pkg} with yum') - returncode, stdout, stderr = run_cmd(cmd) - if returncode != 0: - print(f"Can't install {pkg}, with yum") - install_failed_pkgs.add(pkg) - else: - print(f'Successfully installed {pkg} with yum') - - if install_failed_pkgs: - exit_status( - "Error", f'Please install {install_failed_pkgs}, then retry') - - -def gen_xml_and_dump(abi_name, target_path, dev_pkgs_set, libs_pkgs_set): - headers_list = list() - for pkg in dev_pkgs_set: - cmd = f'rpm -ql {pkg} | grep .*include.*\.h$' - returncode, stdout, stderr = run_cmd(cmd) - headers_list.append(stdout.decode()) - - libs_list = list() - for pkg in libs_pkgs_set: - cmd = f'rpm -ql {pkg} | grep .*lib.*\.so\.*$' - returncode, stdout, stderr = run_cmd(cmd) - libs_list.append(stdout.decode()) - - file = f'{target_path}/NEW-dump.xml' - with open(file, 'wt+') as f: - f.write("\n") - f.write('1.0\n') - f.write('\n') - - f.write("\n") - f.writelines(headers_list) - f.write("\n") - - f.write("\n") - f.writelines(libs_list) - f.write("\n") - dump_file = f'{target_path}/NEW-abi.dump' - cmd = f'abi-compliance-checker -xml -l {abi_name} -dump {file} -dump-path {dump_file}' - print(f'Analyzing the symbols of {libs_pkgs_set} ...') - run_cmd(cmd) - - return dump_file - - -def compare_syms(export_dir, dump_file): - print('Checking symbol differences ...') - elf_sym_set = set() - elf_file = f'{export_dir}/OLD-elf.info' - with open(f'{elf_file}', 'rt') as f: - elf_symbol_fmt = ' *(?P[0-9]*): (?P[0-9abcdef]*) (?P[0-9]*).*(FUNC).*@.*' - for line in f: - m = re.match(elf_symbol_fmt, line) - if not m: - continue - elf_line_list = re.split(r'\s+', line.strip()) - sym = elf_line_list[7].split('@')[0] - - elf_sym_set.add(sym) - - library_sym_list = set() - - with open(f'{dump_file}', 'r') as file: - context = file.read().replace('& ', '') - - with open(f'{dump_file}', 'w+') as file: - file.writelines(context) - - tree = ET.parse(f'{dump_file}') - for line in tree.iterfind('symbols/library/symbol'): - sym = line.text.split('@@')[0] - library_sym_list.add(sym) - diff_syms_list = list((elf_sym_set - library_sym_list)) - old_syms_list = list(elf_sym_set) - - return [diff_syms_list, old_syms_list] - - -def output_result(syms_list, export_dir): - df1 = pd.DataFrame(syms_list[0], columns=[u'当前系统缺少符号']) - df2 = pd.DataFrame(syms_list[1], columns=[u'二进制依赖符号']) - - result = pd.concat([df1, df2], axis=1) - # 用空替换表格中的 NaN - result = result.fillna('') - - html = result.to_html() - csv = result.to_csv() - - file = f'{export_dir}/result.html' - with open(file, 'wt+') as f: - f.writelines(html) - html_path = os.path.realpath(file) - - file = f'{export_dir}/result.csv' - with open(file, 'wt+') as f: - f.writelines(csv) - - print(f'The check result is {html_path}') - - def s_exit(code): sys.exit(ERROR_CODE[code]) - def print_err(msg): sys.stderr.write(msg+"\n") - def exit_status(code, msg): if code != "Ok": print_err("ERROR: "+msg) @@ -238,42 +70,6 @@ def exit_status(code, msg): s_exit(code) - -def check_cmd(prog): - for path in os.environ['PATH'].split(os.pathsep): - path = path.strip('"') - candidate = path+"/"+prog - if os.path.isfile(candidate) and os.access(candidate, os.X_OK): - return candidate - return None - - -def get_abi_name(export_dir): - file = f'{export_dir}/OLD-name.info' - if not os.access(f'{file}', os.R_OK): - exit_status( - "Error", f"The info file {file} can't be read, Please check") - with open(file, 'r') as f: - name = f.read() - return name - - -def gen_dump_with_debuginfo(basename, export_dir, debuginfo_pkgs): - # TODO:当前实现有困难 - pass - - -def check_dump_syms(abi_name, export_dir): - new_dump = f'{export_dir}/NEW-abi.dump' - old_dump = f'{export_dir}/OLD-abi.dump' - syms_list = f'{export_dir}/OLD-func-syms.info' - html = f'{export_dir}/export.html' - cmd = f'abi-compliance-checker -l {abi_name} -old {old_dump} -new {new_dump} --symbols-list {syms_list} --report-path {html}' - print(f'Checking the symbols of {abi_name} ...') - run_cmd(cmd) - return html - - def main(): global ARGS ARGS = parse_args() diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..2ac5bfec09cccb2b328c80bfcaff0dd9d5f9d4af --- /dev/null +++ b/src/utils.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging +import os +import re +import stat +import subprocess +import sys + +import xml.etree.ElementTree as ET +import pandas as pd + +def run_cmd(cmd): + ''' + run command in subprocess and return exit code, output, error. + ''' + os.putenv('LANG', 'C') + os.putenv('LC_ALL', 'C') + os.environ['LANG'] = 'C' + os.environ['LC_ALL'] = 'C' + cmd = cmd.encode('UTF-8') + proc = subprocess.Popen(cmd, shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + (stdout, stderr) = proc.communicate() + returncode = proc.returncode + return (returncode, stdout, stderr) + +def find_devel_lib_package(export_dir): + print('Checking the dependency ...') + file = f'{export_dir}/OLD-pkgs.info' + if not os.access(f'{file}', os.R_OK): + exit_status( + "Error", f"The info file {file} can't be read. Please check") + + dev_pkgs_set = set() + libs_pkgs_set = set() + dbginfo_pkgs_set = set() + with open(file, 'r') as f: + for line in f: + rpm_name = get_rpmname_without_libs(line) + dev_pkgs_set.add(f'{rpm_name}-devel') + dbginfo_pkgs_set.add(f'{rpm_name}-debuginfo') + # 包名中包含 lib 关键字的包,本身就是 lib 包 + if 'lib' not in rpm_name: + libs_pkgs_set.add(f'{rpm_name}-libs') + else: + libs_pkgs_set.add(f'{rpm_name}') + return dev_pkgs_set, libs_pkgs_set, dbginfo_pkgs_set + +def try_install_packages(dev_pkgs, libs_pkgs=set()): + not_installed_pkgs = set() + + dev_pkgs = set(dev_pkgs) | set(libs_pkgs) + for pkg in dev_pkgs: + cmd = f'rpm -q {pkg}' + returncode, stdout, stderr = run_cmd(cmd) + if returncode != 0: + print(f"It seems that the OS doesn't install {pkg}") + not_installed_pkgs.add(pkg) + else: + print(f'The packages "{pkg}" have been installed') + + install_failed_pkgs = set() + for pkg in not_installed_pkgs: + cmd = f'yum install -y {pkg}' + print(f'Trying to install package {pkg} with yum') + returncode, stdout, stderr = run_cmd(cmd) + if returncode != 0: + print(f"Can't install {pkg}, with yum") + install_failed_pkgs.add(pkg) + else: + print(f'Successfully installed {pkg} with yum') + + if install_failed_pkgs: + exit_status( + "Error", f'Please install {install_failed_pkgs}, then retry') + + +def gen_xml_and_dump(abi_name, target_path, dev_pkgs_set, libs_pkgs_set): + headers_list = list() + for pkg in dev_pkgs_set: + cmd = f'rpm -ql {pkg} | grep .*include.*\.h$' + returncode, stdout, stderr = run_cmd(cmd) + headers_list.append(stdout.decode()) + + libs_list = list() + for pkg in libs_pkgs_set: + cmd = f'rpm -ql {pkg} | grep .*lib.*\.so\.*$' + returncode, stdout, stderr = run_cmd(cmd) + libs_list.append(stdout.decode()) + + file = f'{target_path}/NEW-dump.xml' + with open(file, 'wt+') as f: + f.write("\n") + f.write('1.0\n') + f.write('\n') + + f.write("\n") + f.writelines(headers_list) + f.write("\n") + + f.write("\n") + f.writelines(libs_list) + f.write("\n") + dump_file = f'{target_path}/NEW-abi.dump' + cmd = f'abi-compliance-checker -xml -l {abi_name} -dump {file} -dump-path {dump_file}' + print(f'Analyzing the symbols of {libs_pkgs_set} ...') + run_cmd(cmd) + + return dump_file + +def compare_syms(export_dir, dump_file): + print('Checking symbol differences ...') + elf_sym_set = set() + elf_file = f'{export_dir}/OLD-elf.info' + with open(f'{elf_file}', 'rt') as f: + elf_symbol_fmt = ' *(?P[0-9]*): (?P[0-9abcdef]*) (?P[0-9]*).*(FUNC).*@.*' + for line in f: + m = re.match(elf_symbol_fmt, line) + if not m: + continue + elf_line_list = re.split(r'\s+', line.strip()) + sym = elf_line_list[7].split('@')[0] + + elf_sym_set.add(sym) + + library_sym_list = set() + + with open(f'{dump_file}', 'r') as file: + context = file.read().replace('& ', '') + + with open(f'{dump_file}', 'w+') as file: + file.writelines(context) + + tree = ET.parse(f'{dump_file}') + for line in tree.iterfind('symbols/library/symbol'): + sym = line.text.split('@@')[0] + library_sym_list.add(sym) + diff_syms_list = list((elf_sym_set - library_sym_list)) + old_syms_list = list(elf_sym_set) + + return [diff_syms_list, old_syms_list] + + +def check_cmd(prog): + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('"') + candidate = path+"/"+prog + if os.path.isfile(candidate) and os.access(candidate, os.X_OK): + return candidate + return None + + +def get_abi_name(export_dir): + file = f'{export_dir}/OLD-name.info' + if not os.access(f'{file}', os.R_OK): + exit_status( + "Error", f"The info file {file} can't be read, Please check") + with open(file, 'r') as f: + name = f.read() + return name + + +def check_dump_syms(abi_name, export_dir): + new_dump = f'{export_dir}/NEW-abi.dump' + old_dump = f'{export_dir}/OLD-abi.dump' + syms_list = f'{export_dir}/OLD-func-syms.info' + html = f'{export_dir}/export.html' + cmd = f'abi-compliance-checker -l {abi_name} -old {old_dump} -new {new_dump} --symbols-list {syms_list} --report-path {html}' + print(f'Checking the symbols of {abi_name} ...') + run_cmd(cmd) + return html + + +def output_result(syms_list, export_dir): + df1 = pd.DataFrame(syms_list[0], columns=[u'当前系统缺少符号']) + df2 = pd.DataFrame(syms_list[1], columns=[u'二进制依赖符号']) + + result = pd.concat([df1, df2], axis=1) + # 用空替换表格中的 NaN + result = result.fillna('') + + html = result.to_html() + csv = result.to_csv() + + file = f'{export_dir}/result.html' + with open(file, 'wt+') as f: + f.writelines(html) + html_path = os.path.realpath(file) + + file = f'{export_dir}/result.csv' + with open(file, 'wt+') as f: + f.writelines(csv) + + print(f'The check result is {html_path}') +