一. 前言:
Linux内核是当今最流行的操作系统, 没有之一, 已经运行在数十亿甚至上百亿的设备上, 如台式电脑, 嵌入式设备, 智能手机(android)等等, 基本上能跑操作系统的,除了几个特别的产家如苹果或微软外(也不完全是这两家), 就基本都是Linux操作系统了, 若你的工作中涉及Linux内核源代码的修改, 这其中或许是内核操作系统部分, 又或是Linux设备驱动类, 你若涉及这些工作, 都不可避免地需要学习,修改或为Linux内核增加代码, 在此之前, 肯定得看内核源码或学习相关内核源码(包括内核驱动部分, 后续不再赘述). Linux之所以如此流程,当然也少不了太多的Linux代码的支持了,一个如此流程且功能全面的操作系统,当然是由太多太多的内核源代码组成,且随着时间的推移,功能越来越多, 支持的平台也越来越多, 内核源码也越来越庞大, 无论对于初学者还是工作多年的老鸟, 要想确定项目中或学习中所使用到的内核源码中, 哪些源码是被使用到, 是一个无法避开的问题, 否则你无法知道需要更改哪个文件, 查找哪些函数或功能或变量在哪些地方被使用到, 这是作为内核程序员无法回避的问题. 在如此庞大的内核源文件中, 快速找到对应的源码, 是个非常实际且非常重要的工作. 如你是否考虑把使用到的内核的源码放到一个工程中来学习或查看, 如Source Insight, 或vim+ctags+global? 这样便于快速定位或查看代码, 或学习或修改, 若把整个内核源代码都加入到查看源码的工程中, 天啦, 如此庞大的源码怎么行呢? 无论加到任何一个代码工具(SI 或 VIM)中, 如此庞大的Linux内核, 如此方法简单让人抓狂, 是否有一方法把只编译到的内核源文件提取出来,加到源码工程种, 便于查看或修改? 那这样就太友好不过了---当然可以, 在此本人把多年前钻研出的绝招分享给大家,希望对大家在Linux内核源码的学习或工作上带来帮助.
二.内核源文件提取原理
不知读者是否知道,一个对应的内核编译后, 在生成vmlinux文件之前, 依赖于所有源文件,每个所源的内核源文件编译时会产生一个 .xx.o.cmd 的隐藏文件. 以 kernel/workqueue.c 文件为例, 内核编译后会生成 .workqueue.o.cmd 文件,其文件内容大致如下, 这样是不是可以发现很多信息了, Linux内核不可能不知道Makefile吧,以及不可能不知道GNU工具链中GCC命令不编译,只生成相应的依赖文件列表,用于Makefile编译,只是该文件已经经过Kernel对应的工具处理过了.
通过上面的示例, 不难知道, 所有编译时生成该文件的, 不就是对应内核配置依赖的源文件了,以及对应源文件所依赖的头文件, 这样,就可通过工具---在此笔者自已实现的, 提取出对应的内核配置所对应内核源码文件列表, 原理是不是很简单?
在此提供提取整指定配置对应的内核源文件列表的脚本kernel_src_dump.py, 脚本源码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ************************************************************************
# * @File : kernel_src_dump.py
# * @Author : LuoQiaoFa@163.com
# * @Date : 2018-11-10 19:02
# * @Version : 1.0
# * @Description: Python Script
# * @License : Copyright (C) 2018-, LuoQiaoFa all rights reserved
#*************************************************************************
import sys
import os
import os.path
import time
import re
import argparse
KERNEL_BASE = os.getcwd()
KERNEL_SRC = os.getcwd()
KERNEL_OUT = os.getcwd()
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--print", help="Only print system command process", action="store_true")
parser.add_argument("-d", "--debug", help="Debug print", action="store_true")
parser.add_argument("-s", "--src", help="kernel source dir")
parser.add_argument("-o", "--out", help="build output dir")
parser.add_argument("-b", "--base", help="kernel base dir")
args = vars(parser.parse_args())
if args["base"]:
kernel_base = args["base"]
KERNEL_BASE = kernel_base
# print("base=%s" % kernel_base)
if args["src"]:
kernel_src = args["src"]
# print("src=%s" % kernel_src)
KERNEL_SRC = kernel_src
if args["out"]:
kernel_out = args["out"]
KERNEL_OUT = kernel_out
# print("out=%s" % kernel_out)
# print(KERNEL_OUT)
if "ANDROID_PRODUCT_OUT" in os.environ:
KERNEL_VERSION = "msm-3.18"
ANDROID_PRODUCT_OUT = os.environ["ANDROID_PRODUCT_OUT"]
relative_path = os.path.join("kernel", KERNEL_VERSION)
kernel_src = os.path.join(os.environ["ANDROID_BUILD_TOP"], relative_path)
kernel_out = os.path.join(ANDROID_PRODUCT_OUT, "obj", "KERNEL_OBJ")
KERNEL_BASE = os.environ["ANDROID_BUILD_TOP"]
idx_start = len(KERNEL_BASE) + 1
KERNEL_SRC = kernel_src[idx_start:]
# print(KERNEL_SRC)
KERNEL_OUT = kernel_out[idx_start:]
# print("KERNEL_OUT=%s" % KERNEL_OUT)
def kernel_depfile_2_flist(depfile, filelist) :
# depfile = "out/target/product/msm8937_64/obj/kernel/msm-3.18/drivers/input/touchscreen/.ft5x06_ts.o.cmd"
# print("processing file: %s ..." % depfile)
fobj = open(depfile, "r")
for line in fobj :
txt = line.strip()
if 0 == len(txt) :
continue
if txt.startswith("cmd_") :
continue
if txt.startswith("deps_") :
continue
if txt.startswith("$(deps_") :
continue
if txt.endswith(".o)") :
continue
if txt.endswith(" \\") :
txt = txt[:-2]
if txt.startswith("$(wildcard ") and txt.endswith(")"):
idx_start = len("$(wildcard ")
tmp_txt = txt[idx_start : -1]
pre_path = KERNEL_OUT
relative_path = os.path.join(pre_path, tmp_txt)
abs_path = relative_path
if KERNEL_BASE in KERNEL_OUT:
pre_path = KERNEL_OUT[len(KERNEL_BASE) + 1::]
relative_path = os.path.join(pre_path, tmp_txt)
abs_path = os.path.join(KERNEL_BASE, relative_path)
if os.path.exists(abs_path) :
print(relative_path)
pass
else :
tmp_txt = re.sub(r".*:= *","", txt)
if len(tmp_txt) == 0 :
continue
if tmp_txt.find(KERNEL_BASE) >= 0 :
idx_start = len(KERNEL_BASE) + 1
full_txt = tmp_txt[idx_start:]
else :
if tmp_txt.endswith(".mod.c") :
continue
pre_path = KERNEL_OUT
relative_path = os.path.join(pre_path, tmp_txt)
if KERNEL_BASE in KERNEL_OUT:
pre_path = KERNEL_OUT[len(KERNEL_BASE) + 1::]
relative_path = os.path.join(pre_path, tmp_txt)
full_txt = relative_path
print(full_txt)
fobj.close()
def depfile_find(path, callback, args):
for root, dirs, files in os.walk(path): # path 为根目录
for fname in files :
rootname = str(root)
if rootname.startswith(os.path.join(KERNEL_OUT,"scripts")) :
continue
full_name = os.path.join(rootname, fname)
matchObj = re.search( r'.*\.o\.cmd', full_name, re.M|re.I)
if matchObj :
# print(full_name)
# print("callback type: %s" % str(type(callback)))
# if "<class 'function'>" == str(type(callback)) :
# py_ver = sys.version[:3]
if "function" in str(type(callback)) :
callback(full_name, args)
if __name__ == '__main__' :
# depfile = "out/target/product/msm8937_64/obj/kernel/msm-3.18/crypto/asymmetric_keys/.x509-asn1.o.cmd"
# kernel_depfile_2_flist(depfile, "filelist")
# path = "out/target/product/msm8937_64/obj/KERNEL_OBJ/crypto"
path = KERNEL_OUT
depfile_find(path, kernel_depfile_2_flist, "undefined")
# dump all files in path: include/generated
path = os.path.join(KERNEL_OUT,"include", "generated")
for root, dirs, files in os.walk(path): # path 为根目录
rootname = str(root)
for fname in files :
full_txt = os.path.join(rootname, fname)
strip_path = full_txt
if KERNEL_BASE in full_txt:
strip_path = full_txt[len(KERNEL_BASE) + 1::]
print(strip_path)
该脚本使用帮助命令如下:
以本人工作中使用的内核配置为例, 进入内核源码根目录
python kernel_src_dump.py -s $PWD -o $PWD/zynqmp_defconfig -b $PWD | uniq -u > kernel.txt
参数说明:
-s 指内核源码的根路径, 绝对路径
-o 指内核编译时输出或临时文件位置, 对应内核编译命令中make O=xxx 中的 xxx 对应的绝对路径.
-b 由于历史原因, 固定与 -s 参数相同, 除非读者有兴趣自已修改脚本, 这完全没问题.
其实很多年前(2012年前后)本人是使用bash shell 编写的脚本来提取内核源文件列表的, 只是后来学会python后, 发现python实在太过强大, 一般工作相关的小工具都改为使用python了.
这样对应内核配置在该内核配置编译后, 例可通过执行上述脚本提取出所有的内核源文件, 保存在kernel.txt文件中, 然后便可以通过
1. Source Insight 工程, 【Project】--->【Add and Remove Project Files】--->【Add from list…】---> kernel.txt 添加到SI内核源码工程中, 这样无论在源码符号(函数或变量)上的查找或源码阅读,跳转, 非常的快速, 方便, 准确, 对于工作或学习,效率上有极大提高.
2. Vim+Ctags+global对于喜欢只在Linux 下工程的大神们来说---当然本人也更多是以这样的方式工作. 下述命令便是如何生成在
ctags --tag-relative=yes --sort=yes --output-format=u-ctags -f kernel.txt
gtags --gtagslabel new-ctags -f kernel.txt
注: 这里ctags是 Universal Ctags, 不是 Exuberant Ctags, 通常Linux发行版可能不支持, 需要通过源码的方式编译出来
另外global 工具包的使用请查阅相关网络资料, 通过本文及具体示例, 希望读者能举一反三, 学会学习, 会思考会钻研, 这样才可能更深入及提高. 在此不对这些工具及与VIM配合使用专门介绍,作者在此分享自已平常使用的相关脚本及配置
gen_tags.sh
#!/bin/bash -
#===============================================================================
# FILE: gen_tags.sh
# USAGE: ./gen_tags.sh
# DESCRIPTION:
# OPTIONS: ---
# REQUIREMENTS: ---
# BUGS: ---
# NOTES: ---
# AUTHOR: LuoQiaofa (Luoqf), luoqiaofa@163.com
# ORGANIZATION:
# CREATED: 07/03/2019 09:31:17 PM EDT
# REVISION: ---
#===============================================================================
set -o nounset # Treat unset variables as an error
filelist="zynqmp_defconfig/kernel.txt"
# script_path=`dirname $0`
# echo script_path=${script_path}
# cd ${script_path}
export GTAGSROOT=${PWD}
export GTAGSDBPATH=${PWD}/zynqmp_defconfig
echo tags_path:${GTAGSDBPATH}
# cd ${GTAGSDBPATH}/../..
echo src_root=${GTAGSROOT}
ctags --tag-relative=yes --sort=yes --output-format=u-ctags -f ${GTAGSDBPATH}/tags -L ${GTAGSROOT}/${filelist}
gtags --gtagslabel new-ctags -f ${GTAGSROOT}/${filelist} ${GTAGSDBPATH}
xref_kernel.sh
#!/bin/bash -
prj=kernel
export GTAGSROOT=${PWD}
export GTAGSDBPATH=${GTAGSROOT}/zynqmp_defconfig
export TAGS=${GTAGSDBPATH}/tags
#vim/gvim -c "set tags=${TAGS}"
#vim/gvim -t <symbol> --cmd "set tags=${TAGS}"
使用时 source xref_kernel.sh