源码保护技术
对于一些大软件工程,可能有一部分模块代码是从第三方获取来的,并且可能是不开源的。前段时间给别的部门软件组提供了一份快充协议栈的模块,使用静态库的方式提供,提供头文件给用户调用协议栈中的函数及读取全局变量,快充协议栈的输入也是有模块内部调用用户提供的指定函数,或指定的全局变量实现,在工程最后链接的时候会将静态库中未链接的符号链接到外部代码的地址。
今天在看PAN1020的SDK,一款蓝牙SOC,它里面的协议栈是不开源的,厂家提供hex文件,于是在想用户需如何集成hex到自己工程,用户代码要如何与协议栈不开源的代码建立联系。自己的猜想基本是对的,厂家还会提供一份头文件,里面有用户需要调用的函数或全局变量,和静态库不同的是,头文件中不是变量和函数的声明,而是需要让用户知道,所需的全局变量与函数在这份hex文件中的哪个位置。
代码分析
厂家提供的hex文件需要链接到指定地址,以下是PAN1020未开源模块所提供的头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #define STACK_FUN_ADDR 0x00016600
#define SVC_dbg_sys_write_register (*(volatile uint32_t *)(STACK_FUN_ADDR)) typedef void (*dbg_sys_write_register_handler)(void (*call)(uint16_t address, uint16_t data));
#define SVC_appm_init_register (*(volatile uint32_t *)(STACK_FUN_ADDR + 4)) typedef void (*appm_init_register_handler)(void (*call)(void));
#define SVC_hci_init_register (*(volatile uint32_t *)(STACK_FUN_ADDR + 8)) typedef void (*hci_init_register_handler)(void (*call)(bool reset));
#define SVC_hci_send_2_controller_register (*(volatile uint32_t *)(STACK_FUN_ADDR + 12)) typedef void (*hci_send_2_controller_register_handler)(void (*call)(void *param));
#define SVC_attm_svc_create_db_register (*(volatile uint32_t *)(STACK_FUN_ADDR + 16)) typedef void (*attm_svc_create_db_register_handler)(uint8_t (*call)(uint16_t *shdl, uint16_t uuid, uint8_t *cfg_flag, uint8_t max_nb_att, uint8_t *att_tbl, ke_task_id_t const dest_id,const struct attm_desc *att_db, uint8_t svc_perm));
#define SVC_gattc_con_enable_register (*(volatile uint32_t *)(STACK_FUN_ADDR + 20)) typedef void (*gattc_con_enable_register_handler)(void (*call)(uint8_t conidx));
|
一开始,我以为是要将hex链接到0x00016600地址,头文件中给出的是函数在hex中的偏移地址。hex文件是带地址的,我用jflash一看后才明白,hex的起始地址是0,0x00016600地址上存放的是一个网表,用来存放函数的真实地址,网表其实就是函数指针类型的数组。
代码中的(STACK_FUN_ADDR + offset)
其实就是函数指针的地址,所以(*(volatile uint32_t *)(STACK_FUN_ADDR + offset))
就是取出函数指针的值,这个是函数真实所在的地址。头文件中还有函数指针的typedef,可以通过函数指针类型知道每个函数的形参和返回值。
有了函数地址与函数类型,用户就可以通过以下方式去调用库中的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| void app_fun_resgister(void) { ((appm_init_register_handler)SVC_appm_init_register)(appm_init); ((hci_init_register_handler)SVC_hci_init_register)(hci_init); ((hci_send_2_controller_register_handler)SVC_hci_send_2_controller_register)(hci_send_2_controller); ((attm_svc_create_db_register_handler)SVC_attm_svc_create_db_register)(attm_svc_create_db); ((gattc_con_enable_register_handler)SVC_gattc_con_enable_register)(gattc_con_enable); ((gattm_cleanup_register_handler)SVC_gattm_cleanup_register)(gattm_cleanup); ((gattm_create_register_handler)SVC_gattm_create_register)(gattm_create); ((prf_cleanup_register_handler)SVC_prf_cleanup_register)(prf_cleanup); ((prf_create_register_handler)SVC_prf_create_register)(prf_create); ((prf_get_id_from_task_register_handler)SVC_prf_get_id_from_task_register)(prf_get_id_from_task); ((prf_get_task_from_id_register_handler)SVC_prf_get_task_from_id_register)(prf_get_task_from_id); ((prf_init_register_handler)SVC_prf_init_register)(prf_init); ((attm_att_update_perm_register_handler)SVC_attm_att_update_perm_register)(attm_att_update_perm); ((gattm_init_attr_register_handler)SVC_gattm_init_attr_register)(gattm_init_attr); ((hci_basic_cmd_send_2_controller_register_handler)SVC_hci_basic_cmd_send_2_controller_register)(hci_basic_cmd_send_2_controller); ((prf_add_profile_register_handler)SVC_prf_add_profile_register)(prf_add_profile); ((gattc_get_mtu_register_handler)SVC_gattc_get_mtu_register)(gattc_get_mtu); ((attm_init_register_handler)SVC_attm_init_register)(attm_init); ((gattm_init_register_handler)SVC_gattm_init_register)(gattm_init); ((hci_send_2_host_register_handler)SVC_hci_send_2_host_register)(hci_send_2_host); ((attmdb_destroy_register_handler)SVC_attmdb_destroy_register)(attmdb_destroy); ((sleep_handler_register)SVC_sleep_handler_register)(sleep_handler); ((ble_event_handler_register)SVC_ble_event_handler_register)(ble_event_handler); ((rf_init_handler_register)SVC_rf_init_handler_register)(rf_init_handler); ((ble_rx_handler_register)SVC_ble_rx_handler_register)(ble_rx_handler); ((rf_dev_cal_init_handler_register)SVC_rf_dev_cal_init_handler_register)(rf_dev_cal_init_handler); }
|
以上代码为初始化模块中,用户将模块所需要的一些函数注册到模块中。
思考
PAN1020这种方式保护源码,让用户不可见源码,相比我之前封静态库的方式有以下优势:
- 编译模块的时候不需要其他模块的头文件声明,通过模块对外提供注册函数的方式,获取外部函数地址后调用。我之前静态库的方式就需要在编译模块的时候提供其他模块的函数声明头文件,而且如果链接的时候找不到还会链接失败。
- 这种由外部注册函数的方式,模块所依赖其他模块的函数名可以关心,如果用静态库方式就外部函数的函数名就必须与声明的一致。
- 提供给用户hex,比静态库文件的信息更少,静态库中有网表信息,hex中只有二进制文件和地址。
是否可以将一个工程的各模块分开独立,编译成各个hex后再集成?优点:可以使公司的源码不易泄露,每个人都维护自己的模块。
让各个模块编译完hex后的地址都为0开始,hex中网表的前两个为模块init和模块main。用户将所有的hex链接到指定flash地址,配置给os各模块网表起始地址,网表大小,模块调度周期。因为网表的前两个是init和main,所以os可以轻松创建好任务。模块与模块之间调用,由模块向os请求获取特定模块特定网表index的函数地址。