C/C++工程函数重命名

使用clang接口实现工程函数重命名

  • 问题背景
    • 符号冲突
  • 重命名实现

问题背景

在c/c++大项目中我们会使用到其他部门提供的so动态库,如果两个部门函数命名不规范出现符号冲突的几率非常大。历史原因我们需要在几十万代码中对函数重命名,手工来改肯定是无法接受的,在解决问题过程中发现,clang的分词配合sed命令来修改很好提高了效率。

符号冲突

学习地址: https://www.cnblogs.com/skynet/p/3372855.html
书籍: 程序员的自我修养——链接、装载与库
解决:https://blog.csdn.net/found/article/details/105263450

  1. 通一个源文件中出现两个相同符号出现编译错误如:
  #include 
  #include 
  static void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }
  
  void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }
  
  #include 
  #include 
  static void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }
  
  static void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }
  1. 内部static 符号和外部提供so导出冲突,内部使用时使用static函数
  2. 内部导出符号和外部符号冲突,调用时使用内部函数
  3. 程序连接的so导出符号冲突
    1). 程序运行时会加载so动态库对外提供的函数名到全局符号表。如果发现so中函数已经被加载到全局符号表中,后面的函数会被忽略。
 //test1.c  gcc -shared -fPIC -o libtest1.so test1.c
  #include 
  #include 
  void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }

  //test2.c gcc -shared -fPIC -o libtest2.so test2.c
  #include 
  #include 
  void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }
  
  //main.c  gcc main.c -o main  -L./ -ltest1 -ltest2 -Xlinker --rpath ./
  //输出
  #include 
  #include 
  void test(int a);
  void test_a();
  void test_b();
  void main()
  {
      test(1);
  }

2). 连接库加载是按照广度优先加载的 比如 下面依赖会先加载 liba.so libb.so libc.so 最后加载libd.so
main -->libtest1.so
—> libtest2.so—>lib.so
—> libtest3.so

//gcc -shared -fPIC -o lib.so test3.c
  #include 
  #include 
  void test()
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__);
  }

3). 编译时库的加载顺序由 -l的顺序确定

//gcc -shared -fPIC -o libd.so d.c
  #include 
  #include 
  void test(int a)
  {
      printf("file:%s func:%s %d\n",__FILE__, __FUNCTION__, a);
  }

  //gcc -shared -fPIC -o liba.so a.c -L./ -ld
  #include 
  #include 
  void test();
  void test_a()
  {
      test(2);
      printf("file:%s func:%s, test\n",__FILE__, __FUNCTION__);
  }

  //gcc -shared -fPIC -o libb.so b.c
  #include 
  #include 
  void test(char *b)
  {
      printf("file:%s func:%s %s\n",__FILE__, __FUNCTION__, b);
  }
  void test_b()
  {
      printf("file:%s func:%s, test\n",__FILE__, __FUNCTION__);
  }

  #include 
  #include 
  void test(int a);
  void test_a();
  void test_b();
  void main()
  {
      test(1);
      test_a();
      test_b();
  }
  /*
  gcc main.c -o test  -L./ -lb -la -Xlinker --rpath ./
  输出
  Segmentation fault (core dumped)
  
  gcc main.c -o test  -L./ -la -lb -Xlinker --rpath ./
  输出
  Segmentation fault (core dumped)
  
  gcc main.c -o test  -L./ -ld -la -lb -Xlinker --rpath ./
  输出
  file:d.c func:test 1
  file:d.c func:test 2
  file:a.c func:test_a, test
  file:b.c func:test_b, test
  */

重命名实现

# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
"""
实现 c c++ .h 的文件中文件中函数定义和调用批量重命名
用到clang的分词功能 可以指定头文件(包含头文件的才进行替换)
原理
1. 找出包含目标头文件的所有.h文件,包括间接引用的 (使用 grep 过滤#include )
2. 找出包含头文件的所有c c++ 文件
3. 替换头文件和源文件中的 函数定义和调用
4. 函数定义 根据词法 分析得到 所有token  标识符 后面跟 左括号可判断为 函数调用或者函数申明


使用需要安装clang python库才能使用
1.安装dnf install clang 或 yum install clang
2.下载clang 源码https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang-11.0.0.src.tar.xz
3.解压拷贝clang-master\bindings\python\clang 到python库安装目录/usr/lib64/python3.6/site-packages/
4.Config.set_library_file 根据libclang的库来设置

"""
import json
import os
import sys
import subprocess
import clang
import logging
import argparse
from clang.cindex import Index, Config

Config.set_library_file('/usr/lib64/libclang.so.9')

logging.basicConfig(level=logging.DEBUG,
                    format='%(message)s')


def chang_func_name(src_file, rename_map):
    cmds = set()
    index = Index.create()
    tu = index.parse(src_file)
    try:
        tokens = tu.cursor.get_tokens()
        token_objs = []
        for token in tokens:
            token_objs.append({"name": token.spelling, "kind": token.kind, "line": token.location.line})

        cnt = len(token_objs) - 1
        for index in range(cnt):
            token_obj = token_objs[index]
            next_token_obj = token_objs[index + 1]
            for old_name, new_name in rename_map.items():
                if token_obj["kind"] == clang.cindex.TokenKind.IDENTIFIER and token_obj["name"] == old_name \
                        and next_token_obj['name'] == "(":
                    cmd = "sed -i '{} s/{}/{}/g' {}".format(token_obj['line'], token_obj['name'], new_name,
                                                           src_file)

                    # 使用set 放着 一行有两个函数调用 执行两次sed
                    cmds.add(cmd)

        for cmd in cmds:
            logging.info(cmd)
            # ret_code, out = subprocess.getstatusoutput(cmd)
            # if ret_code != 0:
            #     logging.error(out)

    except Exception as e:
        # logging.warning(e)
        pass


def gather_file(root, file, output, endStr):
    exclude = []
    if os.path.islink(root):
        return
    if file in exclude:
        return
    if file.endswith(endStr):
        output.append(root + "/" + file)


def get_include_header_files(header_file, all_headers):
    todo = [os.path.basename(header_file)]
    done_headers = [header_file]
    tmp_file = "/tmp/aaa"
    fd = open(tmp_file, 'w')
    for line in all_headers:
        fd.writelines(line + "\n")
    fd.close()

    while True:
        if len(todo) == 0:
            break
        tmp_header_file = todo.pop(0)
        match_string = tmp_header_file.replace(".", "\\.")
        cmd = "cat {} | xargs grep -l '#include *[\"<]\\(.*/\\)*{}[\">]'".format(tmp_file, match_string)
        logging.debug(cmd)
        ret_code, out = subprocess.getstatusoutput(cmd)
        if out == "":
            continue

        for file_name in out.split('\n'):
            if file_name not in done_headers:
                todo.append(os.path.basename(file_name))
                done_headers.append(file_name)

    return done_headers


def get_all_src_files(include_headers, all_src_files):
    tmp_file = "/tmp/aaa"
    fd = open(tmp_file, 'w')
    for line in all_src_files:
        fd.writelines(line + "\n")
    fd.close()

    need_handle_src_files = []

    for header_file in include_headers:
        match_string = os.path.basename(header_file).replace(".", "\\.")
        cmd = "cat {} | xargs grep -l '#include *[\"<]\\(.*/\\)*{}[\">]'".format(tmp_file, match_string)
        ret_code, out = subprocess.getstatusoutput(cmd)
        logging.debug(cmd)
        if out == "":
            continue
        need_handle_src_files += out.split('\n')

    return list(set(need_handle_src_files))
if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.description = '批量重命名c/c++代码函数名'
    parser.add_argument('--code_path', help='项目工程目录 如/home/', required=True)
    parser.add_argument('--header_file', help='头文件路径 只有包含此头文件才会替换 不指定替换所有文件', required=False)
    parser.add_argument('--func_map_file', required=True,
                        help='指定需要替换的函数名文件全路径 如/home/rename_funcs 文件内容格式为 {"old_func_name1":"new_func_name1", "old_func_name2":"new_func_name2"}')
    args = parser.parse_args()

    code_path = args.code_path
    header_file = args.header_file
    func_map_file = args.func_map_file

    if not os.path.exists(code_path):
        logging.error("code path: {} not exist".format(code_path))
        exit(-1)

    if header_file is not None and not os.path.exists(header_file):
        logging.error("header_file: {} not exist".format(header_file))
        exit(-1)
    try:
        with open(func_map_file) as fp:
            rename_map = json.load(fp)
    except FileNotFoundError as e:
        logging.error("func_map_file: {} not exist".format(func_map_file))
        parser.print_help()
        exit(-1)
    except json.decoder.JSONDecodeError as e:
        logging.error("func_map_file: {} format error".format(func_map_file))
        parser.print_help()
        exit(-1)

    all_headers = []
    all_src_files = []
    for root, dirs, files in os.walk(code_path):
        for file in files:
            gather_file(root, file, all_headers, ".h")
            gather_file(root, file, all_src_files, ".c")
            gather_file(root, file, all_src_files, ".cpp")

    need_hande_files = all_headers + all_src_files
    if header_file is not None:
        include_headers = get_include_header_files(header_file, all_headers)
        logging.debug(include_headers)
        need_hande_files = get_all_src_files(include_headers, all_src_files)
        logging.debug(need_hande_files)
        need_hande_files += include_headers

    for src_file in need_hande_files:
        chang_func_name(src_file, rename_map)

你可能感兴趣的:(编译)