首发于先知论坛

https://xz.aliyun.com/t/9186

概述

DA145x软件平台利用了由Riviera Waves许可的小型高效实时内核,内核提供以下功能:

  1. 任务创建和状态转换。
  2. 任务之间的消息交换。
  3. 计时器管理。
  4. 动态内存分配。
  5. BLE事件的调度和处理

基础数据结构

本节主要分析Riviera Waves系统中常用的一些数据结构

co_list链表实现

数据结构

co_list就是一个单向链表,DA145x代码里面会使用co_list来存放各种数据,比如消息数据.

核心的数据结构如下

//链表中的节点
struct co_list_hdr
{
/// 指向下一个链表节点
struct co_list_hdr *next;
}; /// 链表头的结构
struct co_list
{
// 链表头节点
struct co_list_hdr *first;
// 链表尾节点
struct co_list_hdr *last; // 链表中的节点个数
uint32_t cnt;
// 链表中最多节点数
uint32_t maxcnt;
// 链表中最少节点数
uint32_t mincnt;
};

co_list表示链表头,存放了整个链表的一些元数据,链表节点为co_list_hdr,应用程序使用co_list时会在其特定结构体内部嵌入co_list_hdr和co_list。

链表初始化

co_list_init用于初始化一个链表

void __fastcall co_list_init(struct co_list *list)
{
list->first = 0;
list->last = 0;
list->cnt = 0;
list->maxcnt = 0;
list->mincnt = -1;
}

插入节点

co_list_push_back用于将节点list_hdr插入到链表list的尾部

void __fastcall co_list_push_back(struct co_list *list, struct co_list_hdr *list_hdr)
{
uint32_t cnt; // r1 if ( list->first )
{
list->last->next = list_hdr;
}
else
{
list->first = list_hdr;
}
list->last = list_hdr;
list_hdr->next = 0;
cnt = list->cnt + 1;
list->cnt = cnt;
if ( list->maxcnt < cnt )
{
list->maxcnt = cnt;
}
}
  1. 如果list->first为空,就把list_hdr放到链表头list->first,否则就把该list_hdr放到最后一个节点的末尾
  2. 最后更新list->lastlist->cnt

co_list_push_front用于将节点list_hdr插入到链表list的头部

void __fastcall co_list_push_front(struct co_list *list, struct co_list_hdr *list_hdr)
{
co_list_hdr *v2; // r2
uint32_t v3; // r1 v2 = list->first;
if ( !list->first )
{
list->last = list_hdr;
}
list_hdr->next = v2;
list->first = list_hdr;
v3 = list->cnt + 1;
list->cnt = v3;
if ( list->maxcnt < v3 )
{
list->maxcnt = v3;
}
}

逻辑类似,就是把list_hdr放到链表list->first,然后修正list中相关的字段

节点出链表

co_list_pop_front将头节点出链表

struct co_list_hdr *__fastcall co_list_pop_front(struct co_list *list)
{
struct co_list_hdr *item; // r1
co_list_hdr *v2; // r2
uint32_t v3; // r2 item = list->first;
if ( list->first )
{
v2 = item->next;
list->first = item->next;
if ( !v2 )
{
list->last = 0;
}
v3 = list->cnt - 1;
list->cnt = v3;
if ( list->mincnt > v3 )
{
list->mincnt = v3;
}
}
return item;
}

就是把list->first的元素取出,然后修改list的相关信息。

取出节点

co_list_extract函数用于在list中取出从list_hdr开始的nb_following个节点。

bool __fastcall co_list_extract(struct co_list *list, struct co_list_hdr *list_hdr, int nb_following)
{
bool has_found; // r5
co_list_hdr *pre; // r4
co_list_hdr *cur; // r3
co_list_hdr *new_next; // r1
uint32_t v7; // r1 has_found = 0;
pre = 0;
for ( cur = list->first; cur; cur = cur->next )
{
if ( cur == list_hdr ) // 首先找到 list_hdr 节点
{
has_found = 1;
while ( nb_following > 0 ) // 从list_hdr开始取出nb_following个节点
{
cur = cur->next;
--nb_following; // 如果 nb_following 超过链表长度,就会空指针...
--list->cnt;
}
new_next = cur->next;
if ( pre ) // list_hdr开始的nb_following个节点出链表
{
pre->next = new_next;
}
else
{
list->first = new_next;
}
if ( list->last == cur )
{
list->last = pre;
}
v7 = list->cnt - 1;
list->cnt = v7;
if ( list->mincnt > v7 )
{
list->mincnt = v7;
}
return has_found;
}
pre = cur;
}
return has_found;
}

主要逻辑就是找到list_hdr节点cur,然后从cur开始取出nb_following个节点。

查找节点

co_list_find就是遍历链表找到list_hdr节点

bool __fastcall co_list_find(struct co_list *list, struct co_list_hdr *list_hdr)
{
do
{
list = list->first;
}
while ( list != list_hdr && list );
return list == list_hdr;
}

链表合并

co_list_merge把两个链表合并为一个链表,实际就是把list2的元素挂在list1的末尾

void __fastcall co_list_merge(struct co_list *list1, struct co_list *list2)
{
list1->last->next = list2->first;
list1->last = list2->last;
list2->first = 0;
list1->cnt += list2->cnt;
list2->cnt = 0;
}

事件调度机制

Riviera Waves中实现了事件调度机制,一个任务可以在处理完事情后,通知特定的事件处理函数去进行具体的事物处理。

相关API实现

本节主要分析事件调度相关函数的实现

ke_event_init

该函数主要就是初始化了一个全局变量

void ke_event_init()
{
memset(p_ke_event_table, 0, sizeof(ke_event_table_struct));
}

p_ke_event_table指向一个全局的事件调度管理结构,经过逆向分析其结构体定义如下

struct ke_event_table_struct
{
int pending_event_bits;
int callback_list[6];
};

其中pending_event_bits其中的一些bit用于表示特定的事件是否已经处于pending状态等待系统处理。

callback_list表示每个事件的处理函数的地址

ke_event_callback_set

该函数实际就是向系统注册 event_type 事件对应的处理函数, event_type最大为5,及系统共支持6个事件。

uint8_t __fastcall ke_event_callback_set(uint8_t event_type, void (*p_callback)(void))
{
unsigned int idx; // r2
uint8_t result; // r0 idx = event_type;
result = 3;
if ( idx < 6 )
{
p_ke_event_table->callback_list[idx] = p_callback;
result = 0;
}
return result;
}

ke_event_schedule

ke_event_schedule会检查p_ke_event_table->pending_event_bits中所有事件的状态,如果事件对应的bit为1,就调用对应的回调函数,关键代码如下

unsigned int ke_event_schedule()
{ v0 = p_ke_event_table;
while ( 1 ) // 检查所有事件的状态
{
result = v0->pending_event_bits;
// 根据pending_event_bits找到对应的回调函数
event_callback = *(v0->callback_list + ((4 * (31 - v3)) & 0x3FF));
if ( event_callback )
{
event_callback(); // 调用事件的回调函数
}

ke_event_schedule会在系统运行的特定时机被调用,比如定时器或者某些任务主动调用来让系统处理事件。

此外该函数不会设置事件对应的bit,所以在事件的处理函数,如果事件得到处理要调用ke_event_clear设置对应事件的bit为0.

ke_event_clear

设置某个事件的状态位为0.

bool __fastcall ke_event_clear(unsigned int event)
{
unsigned int v1; // r1
_BOOL4 result; // r0 v1 = __get_CPSR();
_R2 = 1;
__asm { MSR.W PRIMASK, R2 }
if ( event < 6 )
{
p_ke_event_table->pending_event_bits &= ~(1 << event);
}
result = v1 != 0;
__asm { MSR.W PRIMASK, R0 }
return result;
}

ke_event_set

设置某个事件的状态位为1,即通知系统该事件需要处理,ke_event_schedule函数中会调用事件对应的回调函数来处理事件。

bool __fastcall ke_event_set(unsigned int a1)
{
unsigned int v1; // r1
_BOOL4 result; // r0 v1 = __get_CPSR();
_R2 = 1;
__asm { MSR.W PRIMASK, R2 }
if ( a1 < 6 )
{
p_ke_event_table->pending_event_bits |= 1 << a1;
}
result = v1 != 0;
__asm { MSR.W PRIMASK, R0 }
return result;
}

系统注册的事件处理函数

通过查看ke_event_callback_set的交叉引用和参数可以知道系统中注册的事件号及其回调函数的信息如下

https://github.com/hac425xxx/BLE-DA145XX/blob/main/argument_tracker.py#L556

addr: 0x7F08BB2, event: 0x5, callback: lld_evt_deffered_elt_handler @ 0x7F08A6E
addr: 0x7F09CCE, event: 0x0, callback: llm_encryption_done @ 0x7F02744
addr: 0x7F0E5C2, event: 0x3, callback: event_3_callback_func @ 0x7F0E58E
addr: 0x7F0E956, event: 0x4, callback: event_4_callback_func @ 0x7F0E87C
addr: 0x7F1CDEC, event: 0x1, callback: event_1_callback_func @ 0x7F1CCDE
addr: 0x7F1D06C, event: 0x2, callback: event_2_callback_func @ 0x7F1CFFA

任务管理机制

Riviera Waves中实现了任务管理机制,用户可以创建自己的任务来处理特定的事件

任务ID由两个部分组成,高8字节为任务的IDX,低8字节为任务的类型,定义如下

/// Task Identifier. Composed by the task type and the task index.
typedef uint16_t ke_task_id_t; /// Builds the task identifier from the type and the index of that task.
#define KE_BUILD_ID(type, index) ( (ke_task_id_t)(((index) << 8)|(type)) ) /// Retrieves task type from task id.
#define KE_TYPE_GET(ke_task_id) ((ke_task_id) & 0xFF) /// Retrieves task index number from task id.
#define KE_IDX_GET(ke_task_id) (((ke_task_id) >> 8) & 0xFF)

ke_task_create用于创建一个任务,实际就是把任务描述符放到全局任务数组的特定位置

uint8_t __fastcall ke_task_create(uint8_t task_type, const struct ke_task_desc *p_task_desc)
{
idx = task_type; if ( idx < 26 )
{
if ( p_task_desc_table_0[idx] )
{
result = 4;
}
else
{
p_task_desc_table_0[idx] = p_task_desc;
}

任务描述符的结构如下

/// Task descriptor grouping all information required by the kernel for the scheduling.
struct ke_task_desc
{
/// Pointer to the state handler table (one element for each state).
const struct ke_state_handler* state_handler;
/// Pointer to the default state handler (element parsed after the current state).
const struct ke_state_handler* default_handler;
/// Pointer to the state table (one element for each instance).
ke_state_t* state;
/// Maximum number of states in the task.
uint16_t state_max;
/// Maximum index of supported instances of the task.
uint16_t idx_max;
};

state是一个数组,用于表示当前task处于哪些状态,state_max为state数组的大小

开发者可以使用ke_state_set设置task->state的值

void __fastcall ke_state_set(const ke_task_id_t id, const ke_state_t state_id)
{
int state_idx; // r4
ke_task_desc *task; // r2
ke_state_t *v4; // r2 state_idx = HIBYTE(id);
task = 0;
if ( id < 0x1Au )
{
task = p_task_desc_table_0[id];
}
if ( task->idx_max > state_idx )
{
v4 = &task->state[state_idx];
if ( *v4 != state_id )
{
*v4 = state_id;
notify_handle_saved_msg(id); // 通知内核去处理queue_saved中的消息
}
}
}

这个表主要在get_msg_handler函数中被使用,用于任务的状态机。

系统中的任务列表

call ke_task_create on llc_init 0x7F02CBE, task_struct: 0x7F1F1E8
call ke_task_create on lld_init 0x7F06E1E, task_struct: 0x7F1F540
call ke_task_create on llm_init 0x7F09CC6, task_struct: 0x7F1F578
call ke_task_create on gtl_init_func 0x7F0E322, task_struct: 0x7F1F7F0
call ke_task_create on gattc_init 0x7F125BE, task_struct: 0x7F1FE44
call ke_task_create on gattm_init 0x7F13824, task_struct: 0x7F1FF40
call ke_task_create on l2cc_init 0x7F13B7A, task_struct: 0x7F1FFE0
call ke_task_create on gapc_init 0x7F1567C, task_struct: 0x7F2004C
call ke_task_create on gapm_init 0x7F176D4, task_struct: 0x7F201B4

消息调度机制

申请消息

函数通过ke_msg_alloc申请消息,入参分别为消息ID,目的task_id, 源task_id以及消息参数的长度。

void *__fastcall ke_msg_alloc(const ke_msg_id_t id, const ke_task_id_t dest_id, const ke_task_id_t src_id, const uint16_t param_len)
{
size_t v6; // r4
ke_msg *msg; // r0
uint32_t *v9; // r5 v6 = param_len;
msg = ke_malloc(param_len + 16, 2); // 申请内存
msg->hdr.next = -1;
msg->saved = 0;
msg->id = id;
msg->dest_id = dest_id;
msg->src_id = src_id;
msg->param_len = v6;
v9 = msg->param;
memset(msg->param, 0, v6);
return v9;
}

返回值是一个ke_msg结构体的param部分

struct ke_msg
{
struct co_list_hdr hdr; // 链表头,用于后面把消息挂载到co_list链表中
uint32_t saved;
ke_msg_id_t id;
ke_task_id_t dest_id;
ke_task_id_t src_id;
uint16_t param_len; // param 的长度
uint32_t param[1];
};

消息释放

ke_msg_free直接使用 ke_free 释放内存。

int __fastcall ke_msg_free(int a1)
{
return ke_free(a1);
}

ke_msg_free的入参是 ke_msg*,但是ke_msg_alloc返回是ke_msgparam,所以在使用ke_msg_free很有可能出现指针没有减0x10(ke_msg头部的大小)的情况。

消息发送

ke_msg_send用于将特定消息发送到目标任务去处理

bool __fastcall ke_msg_send(int param)
{
ke_msg *msg_hdr; // r1
unsigned int v2; // r4 msg_hdr = (param - 16);
v2 = __get_CPSR();
_R0 = 1;
__asm { MSR.W PRIMASK, R0 } // 关闭中断
co_list_push_back(&p_ke_env->queue_sent, &msg_hdr->hdr);
_R0 = v2 != 0;
__asm { MSR.W PRIMASK, R0 } // 恢复中断
return ke_event_set(1u);
}

主要逻辑就是把msg_hdr放到p_ke_env->queue_sent链表的末尾,p_ke_env指向ke_envke_env是一个全局变量,其结构如下

/// Kernel environment definition
struct ke_env_tag
{
/// Queue of sent messages but not yet delivered to receiver
struct co_list queue_sent;
/// Queue of messages delivered but not consumed by receiver
struct co_list queue_saved;
/// Queue of timers
struct co_list queue_timer; #if (KE_MEM_RW)
/// Root pointer = pointer to first element of heap linked lists
struct mblock_free * heap[KE_MEM_BLOCK_MAX];
/// Size of heaps
uint16_t heap_size[KE_MEM_BLOCK_MAX]; #if (KE_PROFILING)
/// Size of heap used
uint16_t heap_used[KE_MEM_BLOCK_MAX];
/// Maximum heap memory used
uint32_t max_heap_used;
#endif //KE_PROFILING
#endif //KE_MEM_RW
};

可以看的结构体头部是queue_sent,类型为co_list,这个队列用于存放发送的的消息,queue_sent中消息会在后面消息调度时,找到对应的消息处理函数进行处理。

ke_msg_send就是把要发送的消息放到ke_envqueue_sent发送队列中。

消息挂载到queue_sent链表后会调用ke_event_set通知内核,1号事件触发,然后在事件处理函数中会去调用消息对应的处理函数去处理消息。

消息处理

ke_task_init_func函数里面注册了1号事件的处理函数

int ke_task_init_func()
{
memset(p_task_desc_table_0, 0, 0x68u);
return ke_event_callback_set(1u, 0x07F1CCDF);
}

0x07F1CCDF处的函数的关键代码为

int event_1_callback_func()
{
// 从发送队列中取出一个消息
msg = co_list_pop_front(&p_ke_env_->queue_sent);
if ( msg && !ke_is_free(msg) )
{
custom_msg_handler = *custom_msg_handlers_1;
if ( *custom_msg_handlers_1 )
{
// 首先在 custom_msg_handlers 里面搜索消息处理函数
for ( i = 0; ; ++i )
{
handler = &custom_msg_handler[i];
if ( !handler->func )
{
break;
}
if ( msg->dest_id == custom_msg_handler[i].task_id )
{
msg_id = msg->id;
if ( msg_id == handler->id || msg_id == dv_0xFFFF )
{
msg_handle_func = custom_msg_handler[i].func;
if ( !msg_handle_func )
{
break; // 如果匹配就调用回调函数处理
}
goto trigger_callback_func;
}
}
}
}
msg_handle_func = get_msg_handler(msg->id, msg->dest_id);
if ( msg_handle_func )
{
trigger_callback_func:
msg_handle_result = msg_handle_func(msg->id, msg->param, msg->dest_id, msg->src_id);
if ( msg_handle_result )
{
if ( msg_handle_result != 1 && msg_handle_result == 2 )
{
// 处理结果为2,msg保存到queue_saved链表
msg->saved = 1;
co_list_push_back(&p_ke_env_->queue_saved, &msg->hdr);
}
goto out;
}
}
ke_msg_free(msg); // 如果消息处理成功就把msg释放
}
out:
if ( !p_ke_env_->queue_sent.first ) // 如果queue_sent链表为空,清除 event #1 事件
{
ke_event_clear(1u);
}
return result;
}

代码逻辑为

  1. p_ke_env_->queue_sent取出一个消息msg
  2. 根据msg->idcustom_msg_handlers 里面搜索消息处理函数,如果能找到就调用消息处理函数。
  3. 否则调用get_msg_handler根据msg->idmsg->dest_id去目标任务描述符里面搜索处理函数
  4. 找到处理函数msg_handle_func后,调用msg_handle_func对消息进行处理
  5. 如果msg_handle_func返回值为0表示消息处理完毕,后面会使用ke_msg_free释放消息的内存,如果返回值为2,就会把消息放到p_ke_env_->queue_saved链表中
  6. 最后函数会判断queue_sent链表如果没有未处理的消息,就会把 1 号事件清除。

总结

本文主要对Riviera Waves系统中的一些关键API、工作机制进行介绍。

DA14531芯片固件逆向系列(2)- 操作系统底层机制分析的更多相关文章

  1. ②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析

    [时间:2016-09] [状态:Open] [关键词:android,NuPlayer,开源播放器,播放框架,ALooper,AHandler,AMessage] 前文中提到过NuPlayer基于S ...

  2. [工控安全]西门子S7-400 PLC固件逆向分析(一)

    不算前言的前言:拖了这么久,才发现这个专题没有想象中的简单,学习的路径大致是Step7->S7comm->MC7 code->firmware,我会用尽量简短的语言把前两部分讲清楚, ...

  3. VS调试在Win7(vista系列)操作系统下 HttpListener拒绝访问解决办法

    一. VS调试在Win7(vista系列)操作系统下 HttpListener无法绑定多个 指定IP.端口问题 来自:http://www.cnblogs.com/ryhan/p/4195693.ht ...

  4. 操作系统底层原理与Python中socket解读

    目录 操作系统底层原理 网络通信原理 网络基础架构 局域网与交换机/网络常见术语 OSI七层协议 TCP/IP五层模型讲解 Python中Socket模块解读 TCP协议和UDP协议 操作系统底层原理 ...

  5. iOS逆向系列-脱壳

    概述 通过iOS逆向系列-逆向App中使用class-dump工具导出App的Mach-O文件所有头文件.Hopper工具分析App的Mach-O文件代码大概实现.但是这些前体是App的Mach-O没 ...

  6. iOS逆向系列-逆向APP思路

    界面分析 通过Cycript.Reveal. 对于Reveal安装配置可参考配置iOS逆向系列-Reveal 通过Reveal找到内存中的UI对象 静态分析 开发者编写的所有代码最终编译链接到Mach ...

  7. 图解Janusgraph系列-图数据底层序列化源码分析(Data Serialize)

    图解Janusgraph系列-图数据底层序列化源码分析(Data Serialize) 大家好,我是洋仔,JanusGraph图解系列文章,实时更新~ 图数据库文章总目录: 整理所有图相关文章,请移步 ...

  8. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  9. STM32F10X固件库函数——串口清状态位函数分析

    STM32F10X固件库函数——串口清状态位函数分析 最近在测试串口热插拔功能的时候,意外发现STM32F10X的串口库函数中,清理串口状态位函数稍稍有点不解.下面是改函数的源码: /******** ...

  10. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

随机推荐

  1. 14. 迭代器、生成器、模块与包、json模块

    1.迭代器 1.1 迭代器介绍 迭代器是用来迭代取值的工具 每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代 # while循环实现迭代取值 a = [1, 2, 3, 4, 5, ...

  2. 小米13T Pro系统合集:性能与摄影的极致融合,值得你升级的系统ROM

    小米 13T Pro 是一款性能卓越.设计精美的旗舰机型,具备多项领先配置,且在与前一代产品及友商机型的对比中优势明显,值得深入探讨. 性能提升 小米 13T Pro 搭载了最新的 天玑 9200+ ...

  3. Python | os.path.join() method

    Python中的os.path.join()方法可以连接一个或多个路径组件. 此方法将各个路径组成部分,与每个非空部分路径组成部分恰好用一个目录分隔符(" /")连接起来. 如果要 ...

  4. 64.element表单校验注意点

    <!-- 表单验证三要素: --> <!-- ① el-form需要有 model属性[表单数据对象].rules属性[验证规则对象].ref属性[引用字符串] --> < ...

  5. Nessus 安装 笔记

    Nessus 安装 笔记 根据 https://www.zwnblog.com/archives/nessus-jie-shao-yu-an-zhuang#2.kali%E5%AE%89%E8%A3% ...

  6. nicegui太香了,跨平台开发和跨平台运行--使用Python+nicegui实现系统布局界面的开发

    在现今国产化浪潮的驱动下,跨平台或者缩小范围说基于国产化Linux或者基于国产鸿蒙系统的开发才是未来的趋势了,风口浪尖上,我们开发人员也只能顺势而为,本篇随笔介绍在Python开发中,使用使用Pyth ...

  7. 快速部署和测试ingress-nginx:1.9.6

    点击查看代码 下载deploy文件 https://github.com/kubernetes/ingress-nginx/blob/controller-v1.9.6/deploy/static/p ...

  8. Vulnhub 靶机 THE PLANETS: EARTH

    0x01信息收集 1.1.nmap扫描 IP段扫描,确定靶机地址 平扫描 nmap 192.168.1.0/24 扫描结果(部分) Nmap scan report for earth.local ( ...

  9. linux 基础(1)快速查询指令的用法

    --help 几乎所有的指令,都可以使用--help选项进行查询.给命令使用--help选项,就会直接出现一段说明命令的文字. > date --help 用法:date [选项]... [+格 ...

  10. 国内SRC信息收集

    SRC之信息收集 前言: ​ 关于SRC信息收集不在于工具是否全面,工具固然重要,它们能帮我们节省大量的时间收集资产,但不是说我们一定要用到所有工具,收集到某个SRC的所有资产才罢休.资产总会有遗漏, ...