IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 展讯SC8810平台虚拟机分析&在QEMU中模拟运行 -> 正文阅读

[系统运维]展讯SC8810平台虚拟机分析&在QEMU中模拟运行

前言

早在2017年,因为酷爱刷机,把玩了很久的三星手机(GT-S7568)刷成了黑砖,bootloader无法启动,很是头疼。那时候没有现在流行的高通方案方便(可以9008),一旦固件底层损坏,通常维修店只能通过ISP(emmc飞线)重写底层固件。那时的我刚上初二,对这些只有浅薄的理解。我发现展讯也有一个USB下载模式,但三星没有在软件(u-boot-SPL)中实现,于是我只能通过自制一根工程线来进入。之后我尝试了多个工具,直接读写emmc,结果没有一个工具支持。原因是当时展讯出了多个版本的sc8810, 市面工具只支持sc8810g,使用NAND,而三星使用了sc8810es,使用emmc。后来我又找来emmc版本的其他手机的rom,将pac包内文件换成s7568的,用ResearchDownload写进去,还是无法亮屏。

2019年,我在github上翻到了展讯和FirefoxOS合作时开源的u-boot仓库,从提交历史里找到了展讯为三星做s7568的bringup的部分源码。编译下载,依然没有反应。我找到s7568的电路图,它的uart TX和下载模式gpio共用一根线接到了USB的ID端上。好不容易接出uart,却没有任何输出。这种情况下,由于我当时满天满地找8810的资料,对其启动流程有了一定的了解,我决定硬着头皮,把三星的SPL放进IDA,对照u-boot源码看看。

最终发现,三星的SPL除了比展讯简洁以外,最大的区别是它加载三星S-Boot的地址和展讯bringup里的不同。三星选择这个地址一定有其原因,于是我把uboot-spl的地址改成三星的,u-boot正常启动,直接点亮屏幕。有了uboot环境,后面的路就好走多了,不仅解决了问题,还能用开源的uboot换掉三星的SPL和S-Boot底层,支持fastboot。

底层启动流程

展讯默认:

IROM?----(emmc boot0 to IRAM)----> SPL-----(emmc boot1 to SDRAM)---->u-boot----(emmc GPT logical to SDRAM)----> vmjaluna(VLX) + Modem ThreadX + Android Kernel

三星定制:

IROM?----(emmc boot0 to IRAM)----> SPL-----(emmc GPT logical to SDRAM)---->S-Boot----(emmc GPT logical to SDRAM)----> vmjaluna(VLX) + Modem ThreadX + Android Kernel

IROM:展讯SoC内的ROM,Cortex-A5从这里启动,负责从外部flash加载SPL,也负责USB下载模式

SPL:u-boot前的loader,负责解决SDRAM初始化等问题,自身运行在片上IRAM里

u-boot或S-Boot:主要bootloader,负责各种初始化,显示第一屏,从flash读取固件和内核,之前就是把这个刷没了。

虚拟机

在后来修复u-boot支持的过程中,因为遇到不少问题,我才发现这个平台很是有意思:

展讯8810片上自带一个单核Cortex-A5(AP)和一个Ceva-X1622(DSP),性价比超高,CP都省了。他们之前的功能机方案采用ThreadX RTOS,上面既运行了基带协议层,也运行了MMI(手机的用户界面),而基带底层运行在DSP上。8810的基带沿用了之前的ThreadX,几乎就是把SC8800G的拿过来,删掉了MMI部分。于是问题来了,既要运行modem,又要运行安卓,CPU只有一个核,他们又不可能把基带搬进Linux内核(那不是家底都被看光了)。出于成本考虑,展讯采用了虚拟机方案,找到了当时做虚拟化方案的公司Red Bend(前VirtualLogix,再前Jaluna),以便同时运行两个OS。这就有了启动流程中的vmjaluna虚拟机。(以上观点均为臆测,如有差错欢迎指正)

图源:https://www.docin.com/p-1326014066.html

此时又出现了一个问题,Cortex-A5作为A9的精简版,没有ARM Virtuallization Extensions,不支持硬件虚拟化,Red Bend 便采用了半虚拟化方案。从内核源码可以看到半虚拟化用的一个巨大patch:https://github.com/himeno-hamster/sprd-kernel-common/commit/6b455af8469f7b86e09f2a838ede389a4d810d90

VMJALUNA

互联网上能找到的信息基本上就只到这里了。我当时对ARM和虚拟化比较感兴趣,2020年时间较多,便想反汇编这个vmjaluna binary。我找到了一份vmjaluna的debug版,其中保留了一些uart logging。同时我也找到了SC6820的datasheet,(sc6820是sc8810砍掉了 移动3G支持 的版本,pdf可以通用)。

u-boot源码中可以看到,其从emmc上加载了几个镜像:

#define DSP_ADR          0x00020000 //DSP image
#define VMJALUNA_ADR     0x00400000 //VLX 虚拟机
#define FIXNV_ADR        0x00480000 //基带NV,固定数据,IMEI等
#define RUNTIMENV_ADR    0x004a0000 //基带NV,非固定数据
#define MODEM_ADR        0x00500000 //基带RTOS,guestOS 1
#define RAMDISK_ADR      0x05500000 //安卓ramdisk
#define KERNEL_ADR       0x04508000 //安卓内核,guestOS 2

u-boot加载完后设置Linux ATAGs,然后就直接通过vlx_entry()跳转进vmjaluna。

VLX进去之后首先来到图上的Virtuallizer,进行MMU配置,整个0-0x0FFFFFFF(前256M SDRAM)被映射到0xC0000000,然后映射uart等VLX本身要用的设备。

接着进行一些内存置零操作,准备nanokernel环境(虚拟机本身也是个微内核)

void *__fastcall vmjaluna_bconf(int a1)
{
  char *v1; // r4
  int v2; // r5

  v1 = &byte_C043B000;                          
  v2 = 0x2000;
  do
  {
    *v1++ = 0;
    --v2;
  }
  while ( v2 );
  return sub_C043A090(a1);
}

在内置的description table里找到nanokernel入口并跳转:

void *__cdecl sub_C043A090(int a1)
{
  int v1; // r5
  int i; // r4
  char **v3; // r2
  void *result; // r0

  v1 = 0;
  dword_C043A734 = (int)&loc_C043A008;
  bconf_init_sections(16);
  for ( i = 0; ; i += 6 )
  {
    result = &banks_param;
    if ( v1++ >= 6 )
      break;
    v3 = &(&vmjaluna_entries_desc)[i];
    if ( *((_BYTE *)v3 + 16) == 0x11 )
      ((void (*)(void))v3[3])();                // find the type 0x11(nkernel_start)
  }
  return result;
}

终于来到main:

void __fastcall __noreturn nkernel_start(void *a1, int a2)
{
  off_C042BE28 = &unk_C0438740;
  dword_C042BE38 = 0xC04380A0;
  main(dword_C04380A0, a1, a2);
}

void __fastcall __noreturn main(int *a1, void *a2, int atags)
{
//...
  serial_init();
  console_init(0);
  printf("\n%s MH 4.1\n", "Red Bend VLX");
  printf("%s\n\n", "Copyright (c) 2002-2011, Red Bend Software. All rights reserved.");
  if ( *a1 )
  {
    print_nk();
    printf("assertion failed at file main.c line #%d\n", 5804);
    while ( 1 )
      ;
  }
  a1[1] = 1;
  cpu_init();
  dword_C0421628 = atags;
  if ( *(_DWORD *)(atags + 4) != 0x54410001 )   // 0x54410001 ATAG_CORE
  {
//...
}

main函数会初始化串口和NK控制台,CPU,中断控制器, 解析bootloader传来的ATAGs,初始化Timer(但是展讯没做),对各个guestOS进行映射(只有一个物理MMU),初始化nk的OS context,这个context将传给guestOS中的OS Plugin,提供一些半虚拟化的API。

          {
            nk_os_ctx->ready = (NkReady)nkcall_ready;
            nk_os_ctx->hgetc = (NkHistGetc)nkcall_cons_hist_getchar;
            nk_os_ctx->commit = nullsub_6;
            nk_os_ctx->stop = (NkStop)nkcall_stop;
            nk_os_ctx->wakeup = (NkWakeUp)nullsub_7;
            nk_os_ctx->resume = (NkResume)nkcall_resume;
            nk_os_ctx->xpost = (NkXIrqPost)sub_C0409940;
            nk_os_ctx->restart = (NkRestart)nkcall_restart;
            nk_os_ctx->upgrade = nullsub_6;
            nk_os_ctx->binfo = (NkGetBinfo)nkcall_binfo;
            nk_os_ctx->osctx_get = (NkOsCtxGet)nkcall_vcpu_get;
            nk_os_ctx->wsync_all = *(NkWSyncAll *)(MEMORY[0xC] + 4);
            nk_os_ctx->wsync_entry = *(NkWSyncEntry *)(MEMORY[0xC] + 8);
            nk_os_ctx->flush_all = *(NkFlushAll *)(MEMORY[0xC] + 12);
            nk_os_ctx->vfp_get = (NkVfpGet)nkcall_vfp_get;
            nk_os_ctx->vfp_owned = 0;
            nk_os_ctx->dev_alloc = (NkDevAlloc)nkcall_legacy_dev_alloc;
            nk_os_ctx->pmem_alloc = (NkPmemAlloc)nkcall_legacy_pmem_alloc;
            nk_os_ctx->smp_xirq_alloc = (NkSmpXIrqAlloc)nkcall_smp_xirq_alloc;
            nk_os_ctx->smp_dev_add = (NkSmpDevAdd)nkcall_smp_dev_add;
            nk_os_ctx->smp_pdev_alloc = (NkSmpPdevAlloc)nkcall_smp_pdev_alloc;
            nk_os_ctx->smp_pmem_alloc = (NkSmpPmemAlloc)nkcall_smp_pmem_alloc;
            nk_os_ctx->smp_pxirq_alloc = (NkSmpPxirqAlloc)nkcall_smp_pxirq_alloc;
            nk_os_ctx->smp_time = (NkSmpTime)nkcall_smp_time;
            nk_os_ctx->smp_time_hz = (NkSmpTimeHz)nkcall_smp_time_hz;
            nk_os_ctx->os_vectors[6] = (NkVector)sub_C0408794;
            nk_os_ctx->smp_cpu_start = (NkSmpCpuStart)nkcall_smp_cpu_start;
            nk_os_ctx->smp_cpu_stop = (NkSmpCpuStop)nkcall_smp_cpu_stop;
            nk_os_ctx->smp_yield = nkcall_smp_yield;
            nk_os_ctx->smp_relax = (NkSmpRelax)nkcall_smp_relax;
            nk_os_ctx->pad_ops[0] = (nku32_f)nkcall_smp_dxirq_alloc;
            nk_os_ctx->smp_irq_connect = (NkSmpIrqConnect)nkcall_smp_irq_connect;
            nk_os_ctx->smp_irq_disconnect = (NkSmpIrqDisconnect)nkcall_smp_irq_disconnect;
            nk_os_ctx->smp_irq_mask = (NkSmpIrqMask)nkcall_smp_irq_mask;
            nk_os_ctx->smp_irq_unmask = (NkSmpIrqUnmask)nkcall_smp_irq_unmask;
            nk_os_ctx->smp_irq_eoi = (NkSmpIrqEoi)nkcall_smp_irq_eoi;
            nk_os_ctx->smp_irq_affinity = (NkSmpIrqAffinity)nkcall_smp_irq_affinity;
            nk_os_ctx->smp_irq_post = (NkSmpIrqPost)nkcall_smp_irq_post;
            nk_os_ctx->smp_timer_alloc = (NkSmpTimerAlloc)nkcall_smp_timer_alloc;
            nk_os_ctx->smp_timer_free = (NkSmpTimerFree)nkcall_smp_timer_free;
            nk_os_ctx->smp_timer_info = (NkSmpTimerInfo)nkcall_smp_timer_info;
            nk_os_ctx->smp_timer_start_periodic = (NkSmpTimerStartPeriodic)nkcall_smp_timer_start_periodic;
            nk_os_ctx->smp_timer_start_oneshot = (NkSmpTimerStartOneShot)nkcall_smp_timer_start_oneshot;
            nk_os_ctx->smp_timer_stop = (NkSmpTimerStop)nkcall_smp_timer_stop;
          }

还要初始化各种虚拟设备,例如基带与安卓间的通信和网络共享,veth,vbpipe等。最后创建Boot CPU实例。各个虚拟设备(包括虚拟CPU)都有对应的descriptor进行描述,例如一个虚拟CPU里会描述各registers的值,操作系统设定的异常向量表地址等。

再接下来是两个函数,分别初始化CBSP,在BootCPU执行CBSP。CBSP全称Core BSP,是VLX中的Primary guest OS,类似于传统虚拟机如HyperV中的root partition,Xen中的Dom0一样,提供硬件驱动(IRQ,TImer,etc.),实现中断分发,内存映射,虚拟机启动等功能。展讯使用了Embedded CBSP,和VLX编译到同一个binary里。

prepare_vm((int)a1);
start_cbsp(a1);

signed int __fastcall nkcbsp_misc_init(NkOsCtx *a1)
{
  sub_C0415C70(a1);
  if ( !vpic_init() || !timer_init() || !vtick_init() )
    return 0;
  console_irq_init();
  return 1;
}

int __fastcall prepare_vm(int result)
{

//......
  while ( 1 )
  {
//......
      v7 = v6->vm;
      v6->regs[13] = (nku32_f)&v6->stack[254];
      v6->sp_svc = (nku32_f)&v6->stack[254];
      result = 0xC0408B38;
      v8 = v6->vcpuid;
      v6->vcpu_flags = 1;
      v6->pc_svc = 0xC0408B38;//设置SVC模式下的PC寄存器
      v6->regs[0] = (nku32_f)v6;//R0寄存器保存了VCPU结构体,会传进CBSP
      v6->regs[15] = (nku32_f)sub_C040C590;
      v7[6] |= 1 << v8;
      if ( v3[16] == 1 )
      {
        v6->vcpu_flags = 0;
        result = 0xC0408BD8;
        v3[13] = 0xC0408BD8;
      }
    }
  }
}

void __fastcall __noreturn start_cbsp(_DWORD *a1)
{
//......
  if ( v1 )
  {
    if ( MEMORY[0x2C] )
    {
      print_nk();
      printf("assertion failed at file main.c line #%d\n", 3560);
      while ( 1 )
        ;
    }
    if ( !sub_C0417D2C() )
    {
      print_nk();
      printf("assertion failed at file main.c line #%d\n", 3561);
      while ( 1 )
        ;
    }
    if ( !sub_C0417D44((int)v2) )
    {
      print_nk();
      printf("panic at file main.c line #%d: ", 3564);
      printf("Unable to initialize NK CBSP\n");
      while ( 1 )
        ;
    }
  }
  else
  {
    if ( MEMORY[0x2C] )
    {
      v3 = (void (__fastcall *)(int))MEMORY[0x34];
      goto LABEL_20;
    }
    if ( !sub_C0417D2C() )
    {
      print_nk();
      printf("panic at file main.c line #%d: ", 3569);
      printf("No primary VM found\n");
      while ( 1 )
        ;
    }
    print_nk();
    printf("Embedded Core BSP used\n"); //展讯
    if ( !nkcbsp_init(v2) )
    {
      print_nk();
      printf("panic at file main.c line #%d: ", 3573);
      printf("Unable to initialize NK CBSP\n");
      while ( 1 )
        ;
    }
  }
  v3 = (void (__fastcall *)(int))nkcbsp_entry_point;
LABEL_20:
  v4 = v2->cpu;
  if ( !v4 )
  {
    print_nk();
    printf("assertion failed at file main.c line #%d\n", 2909);
    while ( 1 )
      ;
  }
//......
  printf("(%d,%d) starting VCPU 0x%x (pc 0x%x arch 0x%x) [%d]\n", v2->id, v2->vcpuid, v2, v3, 0, LODWORD(v2->ttime));
  v3((int)v2);
}

CBSP里是个大循环,不断在两个虚拟机之间切换,同时不断的开关中断,既保证正常虚拟机调度不被打断,也能及时分发处理guest IRQs。

void __fastcall __noreturn nkcbsp_entry_point(int a1)
{
  j__nkcall_ready((_DWORD *)a1);
  while ( 1 )
  {
    while ( sub_C040844C((NkOsCtx *)a1, *(_DWORD *)(a1 + 2648)) )
      ;
    flip_irq();
  }
}

int __fastcall sub_C040844C(NkOsCtx *a1, int a2)
{
  nku32_f v2; // lr
  NkOsCtx *v4; // r0
  int v5; // r1
  NkOsCtx *v6; // r10

  a1->cur_prio = a2;
  a1->regs[14] = v2;
  v4 = (NkOsCtx *)sub_C0409488(a1);
  v6 = v4;
  if ( v4 == a1 )
    return ((int (__fastcall *)(_DWORD))a1->regs[14])(0); //guest2 R14(LR)
  sub_C0408334((int)v4, v5);
  return ((int (__fastcall *)(_DWORD))v6->regs[14])(0); //guest3 R14(LR)
}

静态分析到这里,就很难进行下去了,估计因为vmjaluna的编译器优化开的很高,IDA出现了很多反编译错误:

    if ( MEMORY[0x2C] )//显然不是访问0x2C,而应该是指针被弄掉了
    {
      v3 = (void (__fastcall *)(int))MEMORY[0x34];

QEMU

IDA是支持动态调试的,动态调试状态下往往能看到很多静态看不到的东西。动态调试有多种方式,比如ARM可以用jtag,sc8810的pdf写着有jtag(ARM or DSP),并且三星也给引出了测试点,但是飞起线来相当麻烦,况且调试起来VLX,modem,Linux要一起上,得有个好jtag仿真器速度才够。于是我选择了另一种方式,通过QEMU模拟SC8810。

要注意的是,QEMU官方并没有对展讯的模拟支持,需要自己实现对sc8810 SoC外设的模拟。我们要模拟运行VLX虚拟机,要模拟CPU,地址空间,中断控制器,定时器,uart。

QEMU官方并不支持模拟Cortex-A5,好在A5/A8/A9软件上差异不大,并且VLX默认带了A5,A8和高通scropion核心的支持,于是我暂时用A8模拟代替一下A5

QEMU自从引入qdev框架以后,采用了面向对象的编程模式,每个虚拟机都有一个“设备树”,由bus和device组成。每个device都会连在它的parent bus上,而一个device也可以提供bus给下级device连接(例如实现I2C,SPI控制器的时候)。

QEMU-machine

QEMU ARM模拟通常是板级模拟,例如模拟S7568这个主板/设备,首先要在hw/arm里建一个samsung-s7568.c,qemu初始化时会实例化这个machine,调用init方法,因此要把主板上各设备的实例化写在里面。

初始化过程除了要给machine添加SoC,内存,还要做好程序的加载。我实现了sc8810 bootrom的加载(但还不支持irom remap,不知道qemu怎么remap)和linux内核的加载(非VLX模式)

static void s7568_init(MachineState *machine)
{
    SC8810State *sc8810;
    Error *err = NULL;

    int irom_size;
    char *filename;

    if (machine->ram_size != 768 * MiB) {
        error_report("This machine can only be used with 768MiB RAM");
        exit(1);
    }
//其实CPU应该放在SoC类初始化里面的,但是还没有A5支持就暂时放这吧
    /* Only allow Cortex-A8 for now before Cortex-A5 support is added */
    if (strcmp(machine->cpu_type, ARM_CPU_TYPE_NAME("cortex-a8")) != 0) {
        error_report("This board only supports cortex-a8 CPU");
        exit(1);
    }

    sc8810 = SPRD_SC8810(object_new(TYPE_SPRD_SC8810));
    object_property_add_child(OBJECT(machine), "soc", OBJECT(sc8810));
    object_unref(OBJECT(sc8810));

    if (!qdev_realize(DEVICE(sc8810), NULL, &err)) {
        error_reportf_err(err, "Couldn't realize Spreadtrum SC8810: ");
        exit(1);
    }

    /*  Does not support IRAM/IROM remap at present  */
    memory_region_init_ram(&sc8810->sdram_0, NULL, "sdram 0",
                            256 * MiB, &error_abort);
    memory_region_init_ram(&sc8810->sdram_1, NULL, "sdram 1",
                            256 * MiB, &error_abort);
    memory_region_init_ram(&sc8810->sdram_2, NULL, "sdram 2",
                            256 * MiB, &error_abort);
    memory_region_add_subregion(get_system_memory(), memmap[SDRAM_0].base,
                                &sc8810->sdram_0);
    memory_region_add_subregion(get_system_memory(), memmap[SDRAM_1].base,
                                &sc8810->sdram_1);
    memory_region_add_subregion(get_system_memory(), memmap[SDRAM_2].base,
                                &sc8810->sdram_2);

    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware);
    if (filename) {
        irom_size = load_image_targphys(filename, memmap[IROM_0].base,
                                        memmap[IROM_0].size);
        g_free(filename);
    } else {
        irom_size = -1;
    }
    if (machine->firmware) {
        if (irom_size < 0 || irom_size > memmap[IROM_0].size) {
            error_report("Could not load sc8810 irom '%s'", machine->firmware);
            exit(1);
        } else {
            CPUState *cs = CPU(&sc8810->cpu);    
            cpu_reset(cs);
            cpu_set_pc(cs, memmap[IROM_0].base); 
        }
    }
    else {
        s7568_binfo.ram_size = machine->ram_size;
        arm_load_kernel(&sc8810->cpu, machine, &s7568_binfo);
    }
}

接下来就要实现SoC类了,我目前实现了sc8810的数字中断控制器(yes,还有个模拟的),3个通用(倒计时)定时器,一个系统计时器(正计时),串口暂时用PL011代替,ADI master没怎么写,但是能过VLX/modem/Linux里的数据校验,不会卡死/panic。

sprd-sc8810.c

static void sc8810_init(Object *obj) //创建8810 SoC里的各个Object
{
    SC8810State *s = SPRD_SC8810(obj);

    object_initialize_child(obj, "cpu", &s->cpu,
                            ARM_CPU_TYPE_NAME("cortex-a8"));
    object_initialize_child(obj, "intc", &s->intc, TYPE_SPRD_SC8810_INTC);

    object_initialize_child(obj, "gptimer", &s->gptimer, TYPE_SPRD_SC8810_GP_TIMER);

    object_initialize_child(obj, "systimer", &s->systimer, TYPE_SPRD_SC8810_SYS_TIMER);

    object_initialize_child(obj, "adi", &s->adi, TYPE_SPRD_SC8810_ADI);

    pl011_create(memmap[UART_0].base, 0, serial_hd(0));
}

static void sc8810_realize(DeviceState *dev, Error **errp) 
//实例化,qdev_realize和sysbus_realize会调用对应对象的实例化方法,作用是,如有些Timer设备需要
//Qemu的ptimer对象,创建ptimer操作在它的realize方法完成
{
    SC8810State *s = SPRD_SC8810(dev);
    SysBusDevice *sysbusdev;
    MemoryRegion *irom = g_new(MemoryRegion, 1);

    if (!qdev_realize(DEVICE(&s->cpu), NULL, errp)) {
        return;
    }
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->intc), errp)) {
        return;
    }
    sysbusdev = SYS_BUS_DEVICE(&s->intc);
    sysbus_mmio_map(sysbusdev, 0, memmap[INTC].base);
    sysbus_connect_irq(sysbusdev, 0,
                       qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_IRQ));//把INTC的IRQ输出连到ARM核的IRQ输入引脚
    sysbus_connect_irq(sysbusdev, 1,
                       qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_FIQ));
    qdev_pass_gpios(DEVICE(&s->intc), dev, NULL);//把SoC中断控制器的中断输入传给主板的dev
//以便于后面的中断源直接连到板上,避免连中断控制器的麻烦

    if (!sysbus_realize(SYS_BUS_DEVICE(&s->gptimer), errp)) {
        return;
    }
    sysbusdev = SYS_BUS_DEVICE(&s->gptimer);
    sysbus_mmio_map(sysbusdev, 0, memmap[GPT].base);
    sysbus_connect_irq(sysbusdev, 0, qdev_get_gpio_in(dev, 5));
    sysbus_connect_irq(sysbusdev, 1, qdev_get_gpio_in(dev, 6));
    sysbus_connect_irq(sysbusdev, 2, qdev_get_gpio_in(dev, 7));

    if (!sysbus_realize(SYS_BUS_DEVICE(&s->systimer), errp)) {
        return;
    }
    sysbusdev = SYS_BUS_DEVICE(&s->systimer);
    sysbus_mmio_map(sysbusdev, 0, memmap[SYST].base);
    sysbus_connect_irq(sysbusdev, 0, qdev_get_gpio_in(dev, 17));

    sysbus_create_simple("l2x0", memmap[PL310].base, NULL);
//...
}

在QEMU中,中断以QEMU GPIO的形式实现,一个设备可以提供gpio in(中断输入) 和 gpio out(中断输出)。SoC realize中要设置各设备的中断输出分别连到中断控制器的哪个输入上

注意:QEMU的中断号(gpio号)是从0开始的

QEMU-devices

实现一个设备的模拟支持便是要模拟软件对其MMIO寄存器的读写,同时模拟硬件的行为,做出对应的响应。

sprd-sc8810-intc.c

static void sprd_sc8810_intc_update(SC8810INTCState *s)
{
    int irq, fiq;

    irq = s->irq_raw_sts & s->irq_enable;
    fiq = s->fiq_raw_sts & s->fiq_enable;

    qemu_set_irq(s->parent_irq, !!irq); //设置 连接ARM IRQ的qemu gpio 状态
    qemu_set_irq(s->parent_fiq, !!fiq); 
}

static void sprd_sc8810_intc_set_irq(void *opaque, int irq, int level)
{
    SC8810INTCState *s = opaque;

    if (level) {
        set_bit(irq, (void *)&s->irq_raw_sts);
        set_bit(irq, (void *)&s->fiq_raw_sts);
    } else {
        clear_bit(irq, (void *)&s->irq_raw_sts);
        clear_bit(irq, (void *)&s->fiq_raw_sts);
    }
    sprd_sc8810_intc_update(s);
}

static uint64_t sprd_sc8810_intc_read(void *opaque, hwaddr offset, unsigned size)
{
    SC8810INTCState *s = opaque;

    switch (offset) {
    case INT_IRQ_MASK_STS:
        return s->irq_raw_sts & s->irq_enable;
    case INT_IRQ_RAW_STS:
        return s->irq_raw_sts;
    case INT_IRQ_ENABLE:
        return s->irq_enable;
    case INT_IRQ_DISABLE:
        break;
    case INT_IRQ_SOFT:
        break;
    case INT_IRQ_TEST_SRC:
    /*   Unimplemented   */
        break;
    case INT_IRQ_TEST_SEL:
    /*   Unimplemented   */
        break;
    case INT_FIQ_MASK_STS:
        return s->fiq_raw_sts & s->fiq_enable;
    case INT_FIQ_RAW_STS:
        return s->fiq_raw_sts;
    case INT_FIQ_ENABLE:
        return s->fiq_enable;
    case INT_FIQ_DISABLE:
        break;
    case INT_FIQ_SOFT:
        break;
    case INT_FIQ_TEST_SRC:
    /*   Unimplemented   */
        break;
    case INT_FIQ_TEST_SEL:
    /*   Unimplemented   */
        break;
    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: Bad offset 0x%x\n",  __func__, (int)offset);
        break;
    }

    return 0;
}

static void sprd_sc8810_intc_write(void *opaque, hwaddr offset, uint64_t value,
                             unsigned size)
{
    SC8810INTCState *s = opaque;

    switch (offset) {
    case INT_IRQ_MASK_STS:
        break;
    case INT_IRQ_RAW_STS:
        break;
    case INT_IRQ_ENABLE:
        s->irq_enable |= value;
        break;
    case INT_IRQ_DISABLE:
        s->irq_enable &= ~value;
        break;
    case INT_IRQ_SOFT:
        s->irq_raw_sts = (s->irq_raw_sts & 0xFFFFFFFD) | (value & 2);
        break;
    case INT_IRQ_TEST_SRC:
    /*   Unimplemented   */
        break;
    case INT_IRQ_TEST_SEL:
    /*   Unimplemented   */
        break;
    case INT_FIQ_MASK_STS:
        break;
    case INT_FIQ_RAW_STS:
        break;
    case INT_FIQ_ENABLE:
        s->fiq_enable |= value;
        break;
    case INT_FIQ_DISABLE:
        s->fiq_enable &= ~value;
        break;
    case INT_FIQ_SOFT:
        s->fiq_raw_sts = (s->fiq_raw_sts & 0xFFFFFFFD) | (value & 2);
        break;
    case INT_FIQ_TEST_SRC:
    /*   Unimplemented   */
        break;
    case INT_FIQ_TEST_SEL:
    /*   Unimplemented   */
        break;
    default:
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: Bad offset 0x%x\n",  __func__, (int)offset);
        break;
    }

    sprd_sc8810_intc_update(s);
}

static const MemoryRegionOps sprd_sc8810_intc_ops = {
    .read = sprd_sc8810_intc_read,
    .write = sprd_sc8810_intc_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

实现IRQ控制器时,对应sc8810的datasheet,软件读写时返回对应的值,保存中断mask状态,并适时更新IRQ状态,触发ARM中断。

同时为了实现虚拟机的挂起/恢复,迁移(虚拟机不关机,搬到另一个物理机上),需要实现设备的VMState

将你设备里需要保存、恢复的状态变量放在VMStateDescription 里就可以了,(有些类型,如qemu_irq,不需要放,也不能放)

static const VMStateDescription vmstate_sprd_sc8810_intc = {
    .name = "sc8810.intc",
    .version_id = 1,
    .minimum_version_id = 1,
    .fields = (VMStateField[]) {
        VMSTATE_UINT32(irq_raw_sts, SC8810INTCState),
        VMSTATE_UINT32(irq_enable, SC8810INTCState),
        VMSTATE_UINT32(fiq_raw_sts, SC8810INTCState),
        VMSTATE_UINT32(fiq_enable, SC8810INTCState),
        VMSTATE_END_OF_LIST()
    }
};

关于TImer

QEMU实现了一个ptimer API,但是ptimer只支持倒计时,也就只适用于倒计时的Timers。sc8810的generic timers是用ptimer模拟的,但是system timer因为没有正计时API,只能自己实现了。自己实现timer,需要用到qemu的几个clock?API,有取虚拟时间的(定时),还有取真实时间(RTC),可以到qemu的include里看看.

static void sprd_sc8810_systimer_reset(DeviceState *dev)
{
    SC8810SYSTState *s = SPRD_SC8810_SYS_TIMER(dev);
    s->alarm = 0xFFFF;
    s->raw_irq_status = 0;
    s->irq_enable = 0;
    s->offset = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);

    timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->alarm);
}

static void sprd_sc8810_systimer_realize(DeviceState *dev, Error **errp)
{
    SC8810SYSTState *s = SPRD_SC8810_SYS_TIMER(dev);
    s->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, sprd_sc8810_systimer_interrupt, s);
}

到此,QEMU上的sc8810模拟支持基本可以用来启动VLX和guests,并连接IDA 调试了

IDA动态调试

首先编译运行qemu,将VLX,modem,Linux加载到对应的地址,设置PC寄存器为VLX入口。

-S? 打开qemu自带的gdbstub,IDA可以连接上去,默认端口1234

-s 开机时暂停虚拟机

qemu-system-arm -M samsung-s7568 -vnc :0 -serial stdio -device loader,file=vmjaluna.bin,addr=0x400000,cpu-num=0 -device loader,file=modem.bin,addr=0x500000 -device loader,file=Image,addr=0x04508000 -monitor tcp::3333,server,nowait -S -s

可不加-S -s 调试器看看效果:

Red Bend VLX MH 4.1
Copyright (c) 2002-2011, Red Bend Software. All rights reserved.

NK: Processor revision r0p0 (ARMv7)
NK: Cache is VIPT dcache line size 64 ?icache line size 64
NK: VFP is present
NK: CRTX-A8 processor found
NK: ?=== dump tag list at 0xc040012c ===
NK: ?tag = 0x54410001 ?size = 0x00000005
NK: ? ?core tag: ? ? ?0x00000000 0x00001000 0x00000000
//......
//......
NK: VM#4 command line: <>
NK: VM#3 command line: < vram=512M show-guest-banks=0x4 no_console_suspend console=ttyNK vdev=(veth,0) vdev=(veth,1) vdev=(veth,2) viomem=* vdev=(vbpipe,0|a;256K) vdev=(vbpipe,2|a;32K)vdev=(vbpipe,3|a;4K) vdev=(vbpipe,4|a;1K) vdev=(vbpipe,5|a;1K) vdev=(vbpipe,6|a;32K) vdev=(vbpipe,7|a;32K) vdev=(vcons,>6|3) vdev=(vcons,6>|xlink=5) linux-timer=virtual vdev=(vclock_framework,>100) vdev=(vaudio,0>|1) root=/dev/ram0 rw init=/init >
NK: VM#2 command line: < vram=21520K vdev=(vcons,5>|xlink=6) guest-reboot vdev=(vcons,>5) vdev=(vcons,>15) vdev=(veth,0) vdev=(veth,1) vdev=(veth,2) vdev=(vcons,15>) ?vdev=(vtimer,0>) vdev=(vbpipe,0|a;8K) vdev=(vbpipe,2|a;32K) vdev=(vbpipe,3|a;4K) vdev=(vbpipe,4|a;1K) vdev=(vbpipe,5|a;1K) vdev=(vbpipe,6|a;32K) vdev=(vbpipe,7|a;32K) vdev=(vtimer,>0) vdev=(vclock_framework,100>) vdev=(vaudio,>0)>
NK: VM#1 command line: <>
NK: NK command line: < vpark=(3) pmem=4M>

NK: nk_pmem_alloc initialized at [0xc1600000-0xc1a00000)
NK: VM#3 uses only 0x0e63b000 < vram=0x20000000
0: ?0x00000000 ?0xc0000000 ? ? ?N ?[0x00000010]
2: ?0xc0000000 ?0x00400000 ? A ? ? [0x00020002]
//......
NK: VLINK name <vcons> link 5 s_id 2 c_id 2 s_info <> c_info <xlink=6>
NK: VLINK name <vcons> link 15 s_id 2 c_id 2 s_info <> c_info <>
//......
NK: DEV ? class 0 id 4e4b494f
NK: DEV * class 0 id 564c4e4b
//......
NK: Embedded Core BSP used
NK: SC8810G: hardware interrupt controller initialized
NK: VPIC: config xirq=513
NK: SC8810G: no timer initialized
NK: vevent_init: no vtimer vlinks found
CBSP: console runs in polling mode
NK: (1,0) starting VCPU 0xc0441040 (pc 0xc0408bb4 arch 0x7dd) [256] //启动CBSP guest
NK: (2,0) starting VCPU 0xc0441ea0 (pc 0xc04ff000) [256] //启动modem OS guest
NK: nk_pmem_alloc(0x1000) [0xc1600000-0xc1a00000)
NK: nk_pmem_alloc(0x1000) [0xc1601000-0xc1a00000)
####: sc8800g_clock_nodes_init() passed!
NKDDI: VPIC-FE initialized
INT:unmask(10) failure : irq not connected
NK: nk_pmem_alloc(0x40000) [0xc1602000-0xc1a00000)
NK: nk_pmem_alloc(0x2000) [0xc1642000-0xc1a00000)
VBPIPE: device vbpipe0 is created for OS#3 link=0
NK: nk_pmem_alloc(0x8000) [0xc1644000-0xc1a00000)
NK: nk_pmem_alloc(0x8000) [0xc164c000-0xc1a00000)
//......
VBPIPE: initialized
NK: VPIC: -> intr_attach(5)
NK: VPIC: -> intr_attach(7)
Booting for the 0 time?
runtime_nv_mem: 0,0,0,0,0,0,0,0,0,0
[_get_dynNV]fixed_nv_info.map_size:16?
[_get_dynNV]ram_nv_table[0].mem:0xc0480000?
[_get_dynNV]ram_nv_table[0].nr_sects:0x80?
[_get_dynNV]runtime_nv_info.map_size:64?
[_get_dynNV]ram_nv_table[1].mem:0xc04a0000?
[_get_dynNV]ram_nv_table[1].nr_sects:0x200?
[_get_dynNV]prod_param_info.map_size:1?
[_get_dynNV]ram_nv_table[2].mem:0xc0490000?
[_get_dynNV]ram_nv_table[2].nr_sects:0x3?
Assert in file nvitem_dummy.c at line 32 info=[] //没加载NV,modem panic,调用AP Linux的modem dump
vbpipe for assert open, waiting for peer open
vbpipe for assert open, waiting for peer open
NK: (3,0) starting VCPU 0xc0442d00 (pc 0xc4500000) [254]// 开始启动Linux guest
[ ? ?0.000000] console [ttyNK0] enabled
[ ? ?0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768
[ ? ?0.000000] pcpu-alloc: [0] 0?
[ ? ?0.000000] Built 1 zonelists in Zone order, mobility grouping on. ?Total pages: 58435
[ ? ?0.000000] Kernel command line: ?show-guest-banks=0x4 no_console_suspend Oonsole=ttyNK viomem=* linux-timer=virtual root=/dev/ram0 rw init=/init console=ttyS1,115200n8 loglevel=8
[ ? ?0.000000] PID hash table entries: 1024 (order: 0, 4096 bytes)
[ ? ?0.000000] Dentry cache hash table entries: 32768 (order: 5, 131072 bytes)
[ ? ?0.000000] Inode-cache hash table entries: 16384 (order: 4, 65536 bytes)
[ ? ?0.000000] Memory: 0MB 0MB 230MB = 230MB total
[ ? ?0.000000] Memory: 170412k/170412k available, 65344k reserved, 0K highmem
[ ? ?0.000000] Virtual kernel memory layout:
[ ? ?0.000000] ? ? vector ?: 0xffff0000 - 0xffff1000 ? ( ? 4 kB)
[ ? ?0.000000] ? ? fixmap ?: 0xfff00000 - 0xfffe0000 ? ( 896 kB)
[ ? ?0.000000] ? ? DMA ? ? : 0xffc00000 - 0xffe00000 ? ( ? 2 MB)
[ ? ?0.000000] ? ? vmalloc : 0xd0800000 - 0xe0000000 ? ( 248 MB)
[ ? ?0.000000] ? ? lowmem ?: 0xc0000000 - 0xd0000000 ? ( 256 MB)
[ ? ?0.000000] ? ? pkmap ? : 0xbfe00000 - 0xc0000000 ? ( ? 2 MB)
[ ? ?0.000000] ? ? modules : 0xbf000000 - 0xbfe00000 ? ( ?14 MB)
[ ? ?0.000000] ? ? ? .init : 0xc4508000 - 0xc452e000 ? ( 152 kB)
[ ? ?0.000000] ? ? ? .text : 0xc452e000 - 0xc4b1a0f8 ? (6065 kB)
[ ? ?0.000000] ? ? ? .data : 0xc4b1c000 - 0xc4b7b2d0 ? ( 381 kB)
[ ? ?0.000000] ? ? ? ?.bss : 0xc4b7b2f4 - 0xc4db7810 ? (2290 kB)
[ ? ?0.000000] Preemptible hierarchical RCU implementation.
[ ? ?0.000000] ?Verbose stalled-CPUs detection is disabled.
[ ? ?0.000000] NR_IRQS:1024
NK: Using virtual PIC
[ ? ?0.000000] sched_clock: 32 bits at 26MHz, resolution 38ns, wraps every 165191ms
NK: VPIC: -> intr_attach(6)
[ ? ?0.000000] Console: colour dummy device 80x30
[ ? ?0.007681] Calibrating delay loop... 695.50 BogoMIPS (lpj=3477504)
[ ? ?0.284053] pid_max: default: 32768 minimum: 301
[ ? ?0.285764] Mount-cache hash table entries: 512
[ ? ?0.295286] CPU: Testing write buffer coherency: ok
[ ? ?0.303002] hw perfevents: enabled with ARMv7 Cortex-A8 PMU driver, 5 counters available
[ ? ?0.308849] L310 cache controller enabled
[ ? ?0.311247] l2x0: 8 ways, CACHE_ID 0x410000c8, AUX_CTRL 0x1e560800, Cache size: 524288 B
[ ? ?0.355362] print_constraints: dummy:?
[ ? ?0.357845] NET: Registered protocol family 16
[ ? ?0.361950] sc8810_init_machine?
[ ? ?0.370240] sc8810_add_misc_devices?
[ ? ?0.371147] hw-breakpoint: debug architecture 0x4 unsupported.
NK: VPIC: -> intr_attach(21)
[ ? ?0.375434] request dma irq ok
NK: VPIC: -> intr_attach(8)
NK: VPIC: -> intr_attach(37)
NK: VPIC: -> intr_attach(33)
[ ? ?0.381447] print_constraints: LDO_VDDARM: 650 <--> 1300 mV at 650 mV normal standby
[ ? ?0.382439] print_constraints: LDO_VDD25: 2500 <--> 3000 mV at 2500 mV normal standby
[ ? ?0.383252] print_constraints: LDO_VDD18: 1200 <--> 2800 mV at 1800 mV normal standby
[ ? ?0.384041] print_constraints: LDO_VDD28: 1800 <--> 3000 mV at 2800 mV normal standby
[ ? ?0.384690] print_constraints: LDO_AVDDBB: 2800 <--> 3100 mV at 3000 mV normal standby
[ ? ?0.385429] print_constraints: LDO_VDDRF0: 1800 <--> 2950 mV at 2850 mV normal standby
[ ? ?0.386163] print_constraints: LDO_VDDRF1: 1800 <--> 2950 mV at 2850 mV normal standby
[ ? ?0.386871] print_constraints: LDO_VDDMEM: 1800 mV normal?
[ ? ?0.387422] print_constraints: LDO_VDDCORE: 650 <--> 1300 mV at 650 mV normal?
[ ? ?0.388094] print_constraints: LDO_LDO_BG: 650 <--> 1300 mV at 650 mV normal?
[ ? ?0.388702] print_constraints: LDO_AVDDVB: 2900 <--> 3400 mV at 3300 mV normal standby
[ ? ?0.389408] print_constraints: LDO_VDDCAMDA: 1800 <--> 3000 mV at 2800 mV normal standby
[ ? ?0.390124] print_constraints: LDO_VDDCAMD1: 1200 <--> 3300 mV at 2800 mV normal standby
//......

接下来连接IDA

Debugger-Process options,配置好QEMU运行的主机ip和port,本机就填127.0.0.1

可以愉快的动态调试了

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-07-04 20:06:08  更:2021-07-04 20:06:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 2:05:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计