zoukankan      html  css  js  c++  java
  • DA14531芯片固件逆向系列(2)- 操作系统底层机制分析

    首发于先知论坛

    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、工作机制进行介绍。

  • 相关阅读:
    VMware workstation安装Windows Server 2012 R2步骤详解(附下载链接)
    英文系统下Oracle SQL Developer 汉化
    C# .NET Core Linux、Windows统一文件路径解决方法
    C# DataTable 转JSON、List 转DataTable、DataTable转List
    IIS无法启动计算机上"."的服务W3SVC(World Wide Web 发布服务)正在停止
    SQLServer 数据库变成单个用户后无法访问的解决方法
    C# webform中无法获取修改后的textbox值
    SqlServer try catch 捕获触发器存储过程异常,结合 transaction 事务
    DataTable.Select筛选过滤数据返回DataRow[]转为DataTable添加到DataSet
    easyui datagrid checkbox复选框取消单击选中事件、初始全选全不选等问题解决
  • 原文地址:https://www.cnblogs.com/hac425/p/14437766.html
Copyright © 2011-2022 走看看