当前位置: 首页 > 科技观察

调试记录-Linux内核静态库封装问题

时间:2023-03-20 01:49:44 科技观察

本文转载自微信公众号《浅谈嵌入式》,作者Vinson。转载本文请联系漫漫嵌入式公众号。背景对于静态库的封装,大多数情况下更多的是在应用层进行封装,使用起来比较熟悉。但是在嵌入式开发中,有时候需要隐藏一些私有的修改,尤其是内核中的一些修改。此时需要在内核态做一个静态库,然后链接到整个内核文件。对于通用(无复杂内核依赖)内核静态库的封装,直接安装应用层封装即可。对于内核中一些高级驱动的私有修改,打包时需要特别注意,包括正确编译,头文件的交叉引用,如果正确链接到内核中,就不会被编译器忽略.封装问题下面以uvc功能驱动usb_f_uvc.ko为例,分析一下内核静态库的封装(假设修改或定制了以下文件)。最后将usb_f_uvc.ko打包成静态库链接到内核中。#kernel/drivers/usb/gadget/function/Makefileusb_f_uvc-y:=f_uvc.ouvc_queue.ouvc_v4l2.ouvc_video.ouvc_configfs.oobj-$(CONFIG_USB_F_UVC)+=usb_f_uvc.o编译我们需要的文件,复杂到一个目录,修改Makefile#Makefile#可以换成自己的工具链CROSS_COMPILE?=arm-linux-gnu-CC:=$(CROSS_COMPILE)gccLD:=$(CROSS_COMPILE)ldAR:=$(CROSS_COMPILE)arCP:=cpRM:=rm#修改正确的内核路径KERNEL_PATH:=xxxx/kerenl#GetgccversionCC_PATH:=${shellwhich$(CC)}CROSS_COMPILE_PATH:=${shelldirname$(CC_PATH)}CFLAGS:=-nostdinc-isystem$(CROSS_COMPILE_PATH)/../lib/gcc/arm-linux-gnu/7.2.0/include#头文件顺序很重要,替换成INCLUDE=-I$(KERNEL_PATH)/arch/arm/include\-I$(KERNEL_PATH)你自己的平台/arch/arm/include/generated/uapi\-I$(KERNEL_PATH)/arch/arm/include/generated\-I$(KERNEL_PATH)/include\-I$(KERNEL_PATH)/arch/arm/include/uapi\-I$(KERNEL_PATH)/include/uapi\-I$(KERNEL_PATH)/include/generated/uapi/\-include$(KERNEL_PATH)/include/linux/kconfig.hINCLUDE+=-I$(KERNEL_PATH)/arch/arm/xxxx/core/include\-I$(KERNEL_PATH)/arch/arm/xxxx/soc-xxx/include\-I$(KERNEL_PATH)/arch/arm/include/asm/mach-generic#CFLAGS+=-fno-delete-null-pointer-checks-Wno-maybe-uninitialized-Wno-frame-address-Wno-format-truncation\#-Wno-format-overflow-Wno-int-in-bool-context-Os--param=allow-store-data-races=0-DCC_HAVE_ASM_GOTO\#-Wframe-larger-than=1024-fno-stack-protector-Wno-unused-but-set-variable-Wno-unused-const-variable\#-fomit-frame-pointer-fno-var-tracking-assignments-Wdeclaration-after-statement\#-Wno-pointer-sign-fno-strict-overflow-fconserve-stack-Werror=implicit-int\#-Werror=strict-prototypes-Werror=date-timeCFLAGS+=-DEXPORT_SYMTAB#这个一定要加CFLAGS+=-D__KERNEL__CFLAGS+=$(INCLUDE)OBJS:=uvc_queue.ouvc_v4l2.ouvc_video.of_uvc.ouvc_configfs。oARFLAG:=-rcsLIB_TARGET:=libxxx.aTARGET:=libxxx.hexall:$(TARGET)%.o:%.c$(CC)$(CFLAGS)-o$@-c$^$(TARGET):$(LIB_TARGET)$(CP)$(LIB_TARGET)$(TARGET)$(CP)-vf$(TARGET)$(KERNEL_PATH)/drivers/usb/gadget/function/$(LIB_TARGET):$(OBJS)$(AR)$(ARFLAG)$@$^clean:find.-name"*.o"|xargsrm-r$(RM)-vf$(LIB_TARGET)$(TARGET)install:$(CP)-vf$(TARGET)$(KERNEL_PATH)/drivers/usb/gadget/function/Makefile参数和头文件是怎么来的?其实整个内核打包过程,我觉得,编译是最难的一步,尤其是第一次接触驱动中符号和宏的定义,以及头文件中层层套娃的包含。根据错误信息,它几乎会崩溃。这里笔者建议先参考【内核编译参数选项】,然后将不相关的选项一一删掉,这样会方便很多。具体操作如下:正常编译内核:touch修改f_uvc.c:重新编译内核:makeuImageV=1>build.txtvimbuild.txt搜索f_uvc看编译信息使用makeV=1参数编译详细信息输出,包括头文件包含顺序,gcc编译参数选项等,然后添加到我们的Makefie中。最后,我们要删除我们的Makfile。加入内核#kernel/drivers/usb/gadget/function/Makefileusb_f_uvc-y:=libxxx.a#obj-$(CONFIG_USB_F_UVC)+=usb_f_uvc.oobj-y+=usb_f_uvc.o#防止Makedistclean清除所有.a编译内核带有$(obj)/libxxx.a:$(obj)/libxxx.hexcp$(obj)/libxxx.hex$(obj)/libxxx.a,并将.a链接到内核。然后烧到板子跑起来。运行实际运行后,发现根本没有链接到板子上。原因分析查看EXPORT_SYMBOL打开Module.symvers,发现uvc相关接口没有导出,猜测可能是没有成功链接到内核。vimModule.symversobjdumpdisassembly使用objdump输出所有的符号表,然后搜索检查进一步确认链接是否正确。结果发现找不到符号信息arm-linux-gnu-objdump-Dzvmlinux>kernel.dump这时一个大胆的想法出现了,难道是编译器优化的?因为是静态库,对于库文件来说,就是一些接口而已,并不能自己执行调用过程。如果没有人调用该接口,是否所有相关符号都自动忽略?考虑一波编译链接分析源码//f_uvc.cDECLARE_USB_FUNCTION_INIT(uvc,uvc_alloc_inst,uvc_alloc);MODULE_LICENSE("GPL");MODULE_AUTHOR("LaurentPinchart");这里的DECLARE_USB_FUNCTION_INIT很重要。让我们扩大。#defineDECLARE_USB_FUNCTION_INIT(_name,_inst_alloc,_func_alloc)\DECLARE_USB_FUNCTION(_name,_inst_alloc,_func_alloc)\staticint__init_name##mod_init(void)\{\returnusb_function_register(&_name##usb_func);\}\static_name(usb_function_unregister(&_name##usb_func);\}\module_init(_name##mod_init);\module_exit(_name##mod_exit)这里的module_init大家应该很熟悉了,对于上面我们封装的库,它本质上就是一个驱动,驱动有对应的入口和出口。对于kernel,所有入口都放在.text.init,加载到内核后会按照相应顺序初始化,如果我们把整个驱动封装成静态库,DECLARE_USB_FUNCTION_INIT属于库的接口,不会被自己调用的,所以在链接的过程中,如果内核发现没有调用关系,自然会忽略libxxx.a的相关符号,知道原因,解决办法就简单了,肯定有place在内核中调用DECLARE_USB_FUNCTION_INIT。方法一:手动调用。不推荐方法二:自动调用。遵循内核驱动模型。从静态库中剥离DECLARE_USB_FUNCTION_INIT并将其他文件打包到库中。修改如下://entry.c#include#include#include#include#include#include#include#include#include#include#include"u_uvc.h"#include"f_uvc.h"staticstructusb_function_instance*uvc_alloc_inst(void){returnuvc_alloc_inst_callback();}staticstructusb_function*uvc_alloc(structusb_function_instance*fi){returnuvc_alloc_callback(fi);}DECLARE_USB_FUNCTION_INIT(uvc,uvc_alloc_inst,uvc_alloc);MODULE_LICENSE("GPL");MODULE_AUTHOR("LaurentPinchart");重新修改Makefileusb_f_uvc-y:=entry.olibxxx.aobj-y+=usb_f_uvc.o#obj-$(CONFIG_USB_F_UVC)+=usb_f_uvc.o$(obj)/libxxx.a:$(obj)/libxxx.hexcp$(obj)/libxxx.hex$(obj)/libxxx.a像这样重新编译内核,就可以使用了。以后只需要更新libxxx.a即可。小结本文简单介绍一下内核静态库以及打包遇到的一些坑。通过一个例子,介绍内核静态库的封装以及遇到的问题。同时也加深了对编译和链接的理解。应用层静态库和内核态库的使用是一样的,只是制作起来有点麻烦。头文件的引用包括编译参数选项,驱动入口相关的部分是否链接成功,不能在库中做,以免踩雷。折腾了别人,原来是链接有问题。