0%

FIBRE_wiki

FIBRE_wiki

Information


  • Authors: Stefan Nagy, Anh Nguyen-Tuong, Jason D. Hiser, Jack W. Davidson,Matthew Hicks
  • Slides: Anonymous

testTL;DR


本文的目标是对二进制程序进行fuzz,主要的做法是将二进制程序提升到IR,然后在IR层面做一些变换,最后将IR重新编译为二进制文件。

Background


Fuzzing

Fuzzing是一种流行的漏洞挖掘的技术,从有无源码的角度来,Fuzzing可以分为基于源码的Fuzzing技术和针对二进制程序的Fuzzing技术。

在基于源码的Fuzzing技术中,目前学术界研究最多的,同时也是应用最广泛的就是覆盖率引导的灰盒Fuzzing技术。代表性的Fuzzer有AFL[1],AFL++[2],libFuzzer[3],honggFuzz[4]等。

基于覆盖率引导的灰盒Fuzzing技术能成功的核心原因在于其应用了遗传算法,可以筛选出优良的种子并且不断遗传变异产生下一代。在遗传算法中,一个很重要的部分是反馈机制,只有通过良好的反馈机制,遗传算法才能筛选出表现良好的种子,从而保留这个种子并对其进行进一步变异,将种子优良的特性遗传下去。在Fuzzing技术中,反馈的收集是通过插装技术来实现的,Fuzzer通过编译时插装,可以在运行时收集覆盖率信息。但是二进制程序覆盖率的收集要困难得多。

Feedback-enhancing Transformation

由上文的分析可知,要想在二进制的Fuzzing中取得良好的效果,必须设计良好的覆盖率反馈机制,所以首先来探讨一下基于源码的Fuzzing技术的优势,才能更好地将这些技巧应用到二进制Fuzzing中。

Instrumentation Pruning

这一技巧的是减少插装的数量,进而降低运行时的开销,AFL就应用了这种技术。AFL工具可以使用AFL_INST_RATIO宏来调节插装的比例。在默认的设置下,AFL_INST_RATIO为100,即AFL默认对所有基本块都进行了插装。但是如果在编译时开启了ASAN,默认的插装比例就会变为33,即插装原来1/3的基本块。当AFL_INST_RATIO为0时,不代表不对基本块进行插装,而是每个函数仅插装一个基本块,收集的是函数之间转移的覆盖率。

AFL_INST_RATIO的实现机制如图所示,通过生成随机数模100实现随机跳过基本块。

Instrumentation Downgrading

这一技巧没有从插装的数量入手分析,而是希望减少插装本身的开销,。AFL默认的覆盖率收集方式是边覆盖率,其实现为:

hash = cur ^ ( prev >> 1 )
bitmap[hash]++

CollAFL[5]对于特定的情况实现了开销更小的方案。对于只有单个前驱基本块的基本块来说,CollAFL的实现方案大大简化为:

bitmap[c] += 1
store _prev, ( _cur >> y )

这样就减少了桩点本身的开销。

Sub-instruction profiling

这一技巧被许多Fuzzer应用,比如laf-Intel[6]和honggfuzz,它所要解决的难点是Magic bytes的问题,如下所示:

if (input == 0xabcdefgh)
    // terrible buggy code
else
    // secure code

通常Fuzzer的变异算法很难恰好变异出符合if条件的输入,Fuzzer可能会在if语句处卡住,无法通过检查。但是如果一个input是0xab000000,另一个input是0xabcdefga,这两个输入在fuzzer看来是一样的,但是实际上第二个input更接近通过if条件检查,sub-instruction profiling技术就是为了解决这个问题。

这种技术通过将较长的常数分解为单个字节的常数实现,比如对这个例子应用Sub-instruction profiling技术就会变成:

if (input >> 24 == 0xab) 
  if ((input & 0xff0000) >> 16 == 0xcd) 
    if ((input & 0xff00) >> 8 == 0xef) 
      if ((input & 0xff) == 0xgh) 
          // terrible buggy code

经过这样的变换,丰富了程序的控制流信息,让Fuzzer对输入的变化更加敏感。

Extra-coverage behavior tracking

这一技术实现了更加精确的覆盖率制导,具体来说就是在覆盖率信息中加入了上下文信息。比如,

A -> B -> C
B -> C -> A

在AFL原生的实现中不能区分上述的情况。

在Angora的实现中,覆盖率使用以下方式记录,这样就实现了更加精准的覆盖率制导技术

(lprev,lcur,hash(callstack))(l_{prev}, l_{cur}, hash(call stack))

Motivation


Binary-only Fuzzing

针对二进制程序的分析可以应用在以下几个方面:

  • 第三方的SDK。有时候我们的代码引入了第三方的库,但是又不想被第三方库的漏洞所拖累,就需要对第三方的SDK进行漏洞挖掘。
  • 特定平台的二进制程序,如高通、三星等。这些程序通常是不开源的,我们只能对二进制程序进行Fuzz
  • 较为复杂的二进制程序。有些程序可能部署起来较为复杂,比如需要模拟器的支持,这是我们只能进行二进制Fuzzing。

Existing Platforms

二进制漏洞挖掘的核心还是要获取覆盖率信息,目前主流的方式有三种:

Hardware-assisted Tracking

第一种主流的方式是使用硬件辅助的方式。目前一些先进的处理器从硬件的角度实现了跟踪PC的功能,其中典型的代表就是Intel PT技术,使用Intel PT技术辅助Fuzzing的典型代表工作是REDQUEEN[7]。使用Intel PT技术的方法包括两个步骤:

  1. 在程序运行的过程中捕获覆盖率信息
  1. 处理捕获到的信息

虽然使用Intel PT技术可以很方便地获取覆盖率信息,但是这种技术也存在一些缺点:

  1. 是需要特殊的硬件支持,并不是一种通用的技术
  1. Intel PT带来的开销在50%左右
  1. 使用Intel PT技术带来方便的同时也造成了不能对二进制程序进行修改

Dynamic Binary Translators

第二种主流的技术是动态二进制翻译,在程序的执行过程中收集覆盖率信息。由于原生的二进制程序并没有实现插装,这种方式是在运行的时候进行动态插装,一个代表性的工作就是AFL的QEMU模式,其工作流程如图所示:

QEMU工作流程

动态二进制翻译方式的优点有:

  1. 支持多种平台。由于这种方式通常利用QEMU等Hypervisor进行动态翻译,所以可以进行跨平台的Fuzzing
  1. 可以获得更多的信息。

动态二进制翻译的缺点就是开销太大, 平均在600%左右

Static Binary Rewriter

第三种主流技术是静态二进制重写,这种技术在运行二进制程序之前对其进行静态改写,实现插装,一个代表性的工作就是AFL-Dyninst[8]。这种方式的缺点也在于Overhead高达500%,同时高度依赖于反汇编的结果。

这三种主流技术的对比如图所示:

下面我们来讨论一下二进制Fuzzing到底应该怎样实现,是否能提出一些准则。

Rewriting versus Translation

首先要讨论的就是采用静态重写的方式还是动态翻译的方式,其对比如下表所示:

动态翻译与静态重写对比

方式优点缺点
动态翻译支持多种架构开销较高
静态重写运行时开销较小依赖反汇编结果

结论:应该采用静态重写方式进行插装

Inlining versus Trampolining

所谓Trampolining的插装方式,是指通过跳到一个payload函数来实现的,但是这种方式的Overhead较高,分别存在于跳到payload和跳回callee的开销。

Inlining的插装方式是指直接将插装代码插入到原来的基本块中。

结论:应该使用inlining的方式进行插装

Real-world Scalability

上面两个讨论的是应该采取的技术路线,下面讨论一下如何让二进制Fuzzing更加有效,对于真实世界的程序扩展性更好。

在这一点上动态翻译方式无疑做得更好,更加灵活。

而静态重写方式的灵活性要差一些,可能存在以下情况不能处理:

  1. 一些静态重写工具只能处理C语言写的程序
  1. 一些静态重写工具只能处理position-independent代码
  1. 一些静态重写工具需要符号表
  1. 一些静态重写工具只能处理Linux兼容的程序

结论:需要支持多种二进制文件的格式和平台

Design


FIBRE的框架如图所示:

主要由两部分组成:

  1. 静态重写引擎,负责将二进制程序提升到IR并从IR重新编译到二进制程序
  1. FIBRE平台,其中包含4个子模块
    1. 控制流优化模块
    1. 控制流分析模块
    1. 选择插装点
    1. 应用插装

Static Rewriting Engine

在开始作者的想法是使用Mcsema[9]作为提升到IR的工具,但是作者由于Mcsema的开销而放弃了。作者最终选择了Zipr[10]这个工具,Zipr基于GCC的IR实现设计了自己的IR,可以达到取得更小的开销。

FIBRE

Optimization

在提升到IR之后,要做的第一件事情就是优化控制流。具体来说有三个步骤:

  1. 给定一个提前定义好的优化标准
  1. 扫描目标二进制的CFG
  1. 应用IR级别的变换

在这一阶段,我们可以应用的技术就是Sub-instruction Profiling

Analysis

第二阶段是对CFG进行详细分析,在这一阶段中会计算一些与CFG相关的元数据,比如前驱-后继和支配关系

Point Selection

第三阶段是选择插装点。在这一阶段中,会遍历所有的基本块选择插装点,有两种基本块例外:

  1. 只有单个后继基本块的基本块
  1. 支配树的叶子节点

Application

在这一阶段中,会实际进行插装操作,在这一阶段中可以实现上下文敏感的插装技术

Evaluation

在评估FIBRE的有效性时,需要回答三个问题:

  1. FIBRE是否在应用编译器级别的插装后仍然保持了性能?
  1. FIBRE应用新的插装技术后是否提升了二进制Fuzzing的效率?
  1. FIBRE是否支持现实程序的Fuzzing?

作者做了实验分别对这几个问题进行了回答

Evaluation-wide Instrumenter Setup

首先我们来看一下实验的设置。

作者进行实验的目标是与目前state-of-the-art的二进制Fuzzing工具进行比较,由于Overhead的原因放弃了AFL-PIN和AFL-DynamoRIO,由于Intel PT不支持插装也没有与它进行比较。最后作者选定的实验目标是AFL-Dyninist和AFL-QEMU

LAVA-M Benchmarking

首先作者在LAVA-M数据集上进行了实验。

选定了四种配置,分别是带字典的空种子、带字典的默认种子、不带字典的空种子和不带字典的默认种子。对于每种配置,作者进行了5次实验,每次实验5个小时,实验结果如图所示。

Fuzzing Real-world Software

第二组对比实验作者选取了真实世界的程序,将每次实验的时间扩展到了24小时,实验结果如图所示:

Real-world Coverage-tracing Overhead

下面作者比较了跟踪覆盖率的开销:

Fuzzing Closed-source Binaries

作者找了一些真实世界的程序进行Fuzz

Conclusion

这篇文章将基于源码的编译器级别的插装扩展到了二进制程序的Fuzzing中,取得了良好的效果。

References

  1. https://lcamtuf.coredump.cx/afl/
  1. Fioraldi A, Maier D, Eißfeldt H, et al. AFL++: Combining incremental steps of fuzzing research[C]//14th {USENIX} Workshop on Offensive Technologies ({WOOT} 20). 2020.
  1. https://llvm.org/docs/LibFuzzer.html
  1. https://github.com/google/honggfuzz
  1. Gan S, Zhang C, Qin X, et al. Collafl: Path sensitive fuzzing[C]//2018 IEEE Symposium on Security and Privacy (SP). IEEE, 2018: 679-696.
  1. Circumventing Fuzzing Roadblocks with Compiler Transformations.
  1. Aschermann C, Schumilo S, Blazytko T, et al. REDQUEEN: Fuzzing with Input-to-State Correspondence[C]//NDSS. 2019, 19: 1-15.
  1. https://github.com/talos-vulndev/afl-dyninst
  1. Dinaburg A, Ruef A. Mcsema: Static translation of x86 instructions to llvm[C]//ReCon 2014 Conference, Montreal, Canada. 2014.
  1. Hawkins W H, Hiser J D, Co M, et al. Zipr: Efficient static binary rewriting for security[C]//2017 47th Annual IEEE/IFIP International Conference on Dependable Systems and Networks (DSN). IEEE, 2017: 559-566.

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

  1. (guest)在kernel/sys.c的syscall中添加hypercall

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    SYSCALL_DEFINE1(interrupt, char __user *, ptr)
    {
    char *kbuf;

    kbuf = kzalloc(256, GFP_KERNEL);
    if(copy_from_user(kbuf, ptr, sizeof(char) * 128)) {
    printk("copy error.\n");
    return -1;
    }

    printk("syscall : %s\n", kbuf);

    kvm_hypercall1(KVM_HC_INTERRUPT, (unsigned long)kbuf);

    return 0;

    }
  2. (guest & host)在include/uapi/linux/kvm_para.h中添加hypercall编号

    1
    #define KVM_HC_INTERRUPT    12
  3. (host)在arch/x86/kvm/x86.c中添加处理程序

    1
    2
    3
    4
    5
    6
    case KVM_HC_INTERRUPT:
    vcpu->run->exit_reason = KVM_EXIT_INTERRUPT;
    vcpu->run->hypercall.args[0] = a0;
    printk("kvm : %lu\n", a0);
    kvm_skip_emulated_instruction(vcpu);
    return 0;
  4. (qemu)在linux-headers/linux/kvm.h中添加hypercall编号

    1
    #define KVM_EXIT_INTERRUPT			29
  5. (qemu)在accel/kvm/kvm-all.c的kvm_cpu_exec函数中添加处理程序

    1
    2
    3
    4
    5
    6
    case KVM_EXIT_INTERRUPT:
    //qemu_log("qemu kvm %lu.\n", run->hypercall.args[0]);
    read_virtual_memory(run->hypercall.args[0], data, x86_64_PAGE_SIZE, cpu);
    qemu_log("qemu kvm %s\n", data);
    ret = 0;
    break;
    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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #define x86_64_PAGE_SIZE    	0x1000
    #define x86_64_PAGE_MASK ~(x86_64_PAGE_SIZE - 1)
    uint8_t data[0x1000];

    bool read_virtual_memory(uint64_t address, uint8_t* data, uint32_t size, CPUState *cpu){
    uint8_t tmp_buf[x86_64_PAGE_SIZE];
    MemTxAttrs attrs;
    hwaddr phys_addr;
    int asidx;

    uint64_t amount_copied = 0;

    //cpu_synchronize_state(cpu);
    kvm_cpu_synchronize_state(cpu);

    /* copy per page */
    while(amount_copied < size){
    uint64_t len_to_copy = (size - amount_copied);
    if(len_to_copy > x86_64_PAGE_SIZE)
    len_to_copy = x86_64_PAGE_SIZE;
    asidx = cpu_asidx_from_attrs(cpu, MEMTXATTRS_UNSPECIFIED);
    attrs = MEMTXATTRS_UNSPECIFIED;
    phys_addr = cpu_get_phys_page_attrs_debug(cpu, (address & x86_64_PAGE_MASK), &attrs);

    if (phys_addr == -1){
    uint64_t next_page = (address & x86_64_PAGE_MASK) + x86_64_PAGE_SIZE;
    uint64_t len_skipped =next_page-address;
    if(len_skipped > size-amount_copied){
    len_skipped = size-amount_copied;
    }

    memset( data+amount_copied, ' ', len_skipped);
    address += len_skipped;
    amount_copied += len_skipped;
    continue;
    }

    phys_addr += (address & ~x86_64_PAGE_MASK);
    uint64_t remaining_on_page = x86_64_PAGE_SIZE - (address & ~x86_64_PAGE_MASK);
    if(len_to_copy > remaining_on_page){
    len_to_copy = remaining_on_page;
    }
    MemTxResult txt = address_space_rw(cpu_get_address_space(cpu, asidx), phys_addr, MEMTXATTRS_UNSPECIFIED, tmp_buf, len_to_copy, 0);
    memcpy(data+amount_copied, tmp_buf, len_to_copy);

    address += len_to_copy;
    amount_copied += len_to_copy;
    }

    return true;
    }

  1. 在include/linux/syscalls.h中添加系统调用声明

    asmlinkage long sys_interrupt(void);

    or

    asmlinkage long sys_interrupt(char __user *ptr);

  2. 在kernel/sys.c中添加系统调用定义

    SYSCALL_DEFINE0(interrupt)

    {

    `printk("Hello world!\n");`
    
    `return 0;`

    }

    or

    SYSCALL_DEFINE1(interrupt, char __user *, ptr)

    {

    char kbuf[256];

if(copy_from_user(kbuf, ptr, sizeof(char) * 128)) {

printk("copy error.\n");

return -1;

}

printk("str : %s\n", kbuf);

return 0;

}

  1. 在arch/x86/entry/syscalls/syscall_64.tbl中添加系统调用项

    439 common interrupt sys_interrupt

  2. 在include/uapi/asm-generic/unistd.h中添加关联

    #define _NR_interrupt 439

    __SYSCALL(_NR_interrupt, sys_interrupt)

    注意syscall总数也需要修改

  3. 重新编译内核

    make -j8

  4. 编写测试函数并编译

    #include <linux/unistd.h>

    #include <sys/syscall.h>

    #include <stdio.h>

    int main(void)

    {

    `long ret = syscall(439);`
    
    `printf("%s %d ret = %ld\n", __func__, __LINE__, ret);`

    return 0;

    }

    or

    \#include <linux/unistd.h>

    \#include <sys/syscall.h>

    \#include <stdio.h>

    ``

    int main(void)

    {

    char buf[256] = "ffffffff";

    char *ptr = buf;

long ret = syscall(439, ptr);

printf("%s %d ret = %ld\n", __func__, __LINE__, ret);

return 0;

}

  1. 进行测试

    [ 243.502496] Hello world!
    main 8 ret = 32768

    or

    [ 118.318246] str : ffffffff
    main 11 ret = 0

下载pixel的源码。

repo init -u https://android.googlesource.com/kernel/manifest -b android-msm-crosshatch-4.9-pie-qpr2
repo sync

修改build.config文件,将config改为build.config.goldfish.arm64.

再修改build.config.goldfish.arm64文件,将KERNEL_DIR改为private/msm-google

在根目录下执行build.build.sh开始编译,出现的错误如下:

1.执行savedefconfig时出现两个config不一致

~~这个命令是最小化配置使用的,将最小化后的配置替换就可以了。

注意自己执行的时候要加上arch,比如make ARCH=arm64 savedefconfig~~

根据make savedefconfig结果来向ranchu64_defconfig中添加条目,不能删减,有时需手动添加。

2.编译出错

arch/arm64/kernel/cpu_errata.c: In function 'arm64_update_smccc_conduit':
arch/arm64/kernel/cpu_errata.c:278:10: error: 'psci_ops' undeclared (first use in this function); did you mean 'sysfs_ops'?

arch/arm64/kernel/cpu_errata.c: In function 'arm64_set_ssbd_mitigation':
arch/arm64/kernel/cpu_errata.c:311:3: error: implicit declaration of function 'arm_smccc_1_1_hvc' [-Werror=implicit-function-declaration]
   arm_smccc_1_1_hvc(ARM_SMCCC_ARCH_WORKAROUND_2, state, NULL);

打补丁加入头文件

diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index 2b9a31a6a16a..1d2b6d768efe 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -16,6 +16,8 @@ 
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */

+#include <linux/arm-smccc.h>
+#include <linux/psci.h>
 #include <linux/types.h>
 #include <asm/cpu.h>
 #include <asm/cputype.h>

3.driver/platform/goldfish中goldfish_pipe出错

找到新版源码替换

4.driver/platform/goldfish中编译goldfish_pipe出现warning: the frame size of xxxx bytes is larger than 2048 bytes

编译内核的堆栈出错,在config中加入

CONFIG_FRAME_WARN=4096

5.编译错误,找不到命令mkdtimg

在aosp目录下执行

mmm system/libufdt/util/src

编译后的工具在out/host/linux-x86/bin/中,加入到环境变量中即可

6.找不到dtbo文件

在Makefile中删除dtbo目标即可

参考

accessibility - 这些驱动提供支持一些辅助设备。在Linux 3.9.4中,这个文件夹中只有一个驱动就是盲文设备驱动。

acpi - 高级配置和电源接口(ACPI : Advanced Configuration and Power Interface)驱动用来管理电源使用。

amba - 高级微控制器总线架构(AMBA : Advanced Microcontroller Bus Architecture)是与片上系统(SoC)的管理和互连的协议。SoC是一块包含许多或所有必要的计算机组件的芯片。这里的AMBA驱动让内核能够运行在这上面。

ata - 该目录包含PATA和SATA设备的驱动程序。串行ATA(SATA)是一种连接主机总线适配器到像硬盘那样的存储器的计算机总线接口。并行ATA(PATA)用于连接存储设备,如硬盘驱动器,软盘驱动器,光盘驱动器的标准。PATA就是我们所说的IDE。

atm - 异步通信模式(ATM : Asynchronous Transfer Mode)是一种通信标准。这里有各种接到PCI桥的驱动(他们连接到PCI总线)和以太网控制器(控制以太网通信的集成电路芯片)。

auxdisplay - 这个文件夹提供了三个驱动。LCD 帧缓存(framebuffer)驱动、LCD控制器驱动和一个LCD驱动。这些驱动用于管理液晶显示器 —— 液晶显示器会在按压时显示波纹。注意:按压会损害屏幕,所以请不要用力戳LCD显示屏。

base - 这是个重要的目录包含了固件、系统总线、虚拟化能力等基本的驱动。

bcma - 这些驱动用于使用基于AMBA协议的总线。AMBA是由博通公司开发。

block - 这些驱动提供对块设备的支持,像软驱、SCSI磁带、TCP网络块设备等等。

bluetooth - 蓝牙是一种安全的无线个人区域网络标准(PANs)。蓝牙驱动就在这个文件夹,它允许系统使用各种蓝牙设备。例如,一个蓝牙鼠标不用电缆,并且计算机有一个电子狗(小型USB接收器)。Linux系统必须能够知道进入电子狗的信号,否则蓝牙设备无法工作。

bus - 这个目录包含了三个驱动。一个转换ocp接口协议到scp协议。一个是设备间的互联驱动,第三个是用于处理互联中的错误处理。

cdrom - 这个目录包含两个驱动。一个是cd-rom,包括DVD和CD的读写。第二个是gd-rom(只读GB光盘),GD光盘是1.2GB容量的光盘,这像一个更大的CD或者更小的DVD。GD通常用于世嘉游戏机中。
char - 字符设备驱动就在这里。字符设备每次传输数据传输一个字符。这个文件夹里的驱动包括打印机、PS3闪存驱动、东芝SMM驱动和随机数发生器驱动等。

clk - 这些驱动用于系统时钟。

clocksource - 这些驱动用于作为定时器的时钟。

connector - 这些驱动使内核知道当进程fork并使用proc连接器更改UID(用户ID)、GID(组ID)和SID(会话ID)。内核需要知道什么时候进程fork(CPU中运行多个任务)并执行。否则,内核可能会低效管理资源。

cpufreq - 这些驱动改变CPU的电源能耗。
cpuidle - 这些驱动用来管理空闲的CPU。一些系统使用多个CPU,其中一个驱动可以让这些CPU负载相当。
crypto - 这些驱动提供加密功能。

dca - 直接缓存访问(DCA : Direct Cache Access)驱动允许内核访问CPU缓存。CPU缓存就像CPU内置的RAM。CPU缓存的速度比RAM更快。然而,CPU缓存的容量比RAM小得多。CPU在这个缓存系统上存储了最重要的和执行的代码。

devfreq - 这个驱动程序提供了一个通用的动态电压和频率调整(DVFS : Generic Dynamic Voltage and Frequency Scaling)框架,可以根据需要改变CPU频率来节约能源。这就是所谓的CPU节能。

dio - 数字输入/输出(DIO :Digital Input/Output)总线驱动允许内核可以使用DIO总线。

dma - 直接内存访问(DMA)驱动允许设备无需CPU直接访问内存。这减少了CPU的负载。

edac - 错误检测和校正( Error Detection And Correction)驱动帮助减少和纠正错误。

eisa - 扩展工业标准结构总线(Extended Industry Standard Architecture)驱动提供内核对EISA总线的支持。

extcon - 外部连接器(EXTernal CONnectors)驱动用于检测设备插入时的变化。例如,extcon会检测用户是否插入了USB驱动器。

esoc-嵌入式只能平台

extcon-用户空间可以监视外部连接器如USB和AC口

firewire - 这些驱动用于控制苹果制造的类似于USB的火线设备。

firmware - 这些驱动用于和像BIOS(计算机的基本输入输出系统固件)这样的设备的固件通信。BIOS用于启动操作系统和控制硬件与设备的固件。一些BIOS允许用户超频CPU。超频是使CPU运行在一个更快的速度。CPU速度以MHz(百万赫兹)或GHz衡量。一个3.7 GHz的CPU的的速度明显快于一个700Mhz的处理器。

gpio - 通用输入/输出(GPIO :General Purpose Input/Output)是可由用户控制行为的芯片的管脚。这里的驱动就是控制GPIO。

gpu - 这些驱动控制VGA、GPU和直接渲染管理(DRM :Direct Rendering Manager )。VGA是640*480的模拟计算机显示器或是简化的分辨率标准。GPU是图形处理器。DRM是一个Unix渲染系统。
hid - 这驱动用于对USB人机界面设备的支持。

hsi - 这个驱动用于内核访问像Nokia N900这样的蜂窝式调制解调器。

hv - 这个驱动用于提供Linux中的键值对(KVP :Key Value Pair)功能。

hwmon - 硬件监控驱动用于内核读取硬件传感器上的信息。比如,CPU上有个温度传感器。那么内核就可以追踪温度的变化并相应地调节风扇的速度。

hwspinlock - 硬件转锁驱动允许系统同时使用两个或者更多的处理器,或使用一个处理器上的两个或更多的核心。

i2c - I2C驱动可以使计算机用I2C协议处理主板上的低速外设。系统管理总线(SMBus :System Management Bus)驱动管理SMBus,这是一种用于轻量级通信的two-wire总线。

ide - 这些驱动用来处理像CDROM和硬盘这些PATA/IDE设备。

idle - 这个驱动用来管理Intel处理器的空闲功能。

iio - 工业I/O核心驱动程序用来处理数模转换器或模数转换器。

infiniband - Infiniband是在企业数据中心和一些超级计算机中使用的一种高性能的端口。这个目录中的驱动用来支持Infiniband硬件。

input - 这里包含了很多驱动,这些驱动都用于输入处理,包括游戏杆、鼠标、键盘、游戏端口(旧式的游戏杆接口)、遥控器、触控、耳麦按钮和许多其他的驱动。如今的操纵杆使用USB端口,但是在上世纪80、90年代,操纵杆是插在游戏端口的。

iommu - 输入/输出内存管理单元(IOMMU :Input/Output Memory Management Unit)驱动用来管理内存管理单元中的IOMMU。IOMMU连接DMA IO总线到内存上。IOMMU是设备在没有CPU帮助下直接访问内存的桥梁。这有助于减少处理器的负载。

ipack - Ipack代表的是IndustryPack。 这个驱动是一个虚拟总线,允许在载体和夹板之间操作。
irqchip - 这些驱动程序允许硬件的中断请求(IRQ)发送到处理器,暂时挂起一个正在运行的程序而去运行一个特殊的程序(称为一个中断处理程序)。

isdn - 这些驱动用于支持综合业务数字网(ISDN),这是用于同步数字传输语音、视频、数据和其他网络服务使用传统电话网络的电路的通信标准。

leds - 用于LED的驱动。

lguest - lguest用于管理客户机系统的中断。中断是CPU被重要任务打断的硬件或软件信号。CPU接着给硬件或软件一些处理资源。

macintosh - 苹果设备的驱动在这个文件夹里。

mailbox - 这个文件夹(pl320-pci)中的驱动用于管理邮箱系统的连接。

md - 多设备驱动用于支持磁盘阵列,一种多块硬盘间共享或复制数据的系统。

media - 媒体驱动提供了对收音机、调谐器、视频捕捉卡、DVB标准的数字电视等等的支持。驱动还提供了对不同通过USB或火线端口插入的多媒体设备的支持。

memory - 支持内存的重要驱动。

memstick - 这个驱动用于支持Sony记忆棒。

message - 这些驱动用于运行LSI Fusion MPT(一种消息传递技术)固件的LSI PCI芯片/适配器。LSI大规模集成,这代表每片芯片上集成了几万晶体管、

mfd - 多用途设备(MFD)驱动提供了对可以提供诸如电子邮件、传真、复印机、扫描仪、打印机功能的多用途设备的支持。这里的驱动还给MFD设备提供了一个通用多媒体通信端口(MCP)层。

misc - 这个目录包含了不适合在其他目录的各种驱动。就像光线传感器驱动。

mmc - MMC卡驱动用于处理用于MMC标准的闪存卡。

mtd - 内存技术设备(MTD :Memory technology devices)驱动程序用于Linux和闪存的交互,这就就像一层闪存转换层。其他块设备和字符设备的驱动程序不会以闪存设备的操作方式来做映射。尽管USB记忆卡和SD卡是闪存设备,但它们不使用这个驱动,因为他们隐藏在系统的块设备接口后。这个驱动用于新型闪存设备的通用闪存驱动器驱动。

net - 网络驱动提供像AppleTalk、TCP和其他的网络协议。这些驱动也提供对调制解调器、USB 2.0的网络设备、和射频设备的支持。

nfc - 这个驱动是德州仪器的共享传输层之间的接口和NCI核心。

ntb - 不透明的桥接驱动提供了在PCIe系统的不透明桥接。PCIe是一种高速扩展总线标准。

nubus - NuBus是一种32位并行计算总线。用于支持苹果设备。

of - 此驱动程序提供设备树中创建、访问和解释程序的OF助手。设备树是一种数据结构,用于描述硬件。

oprofile - 这个驱动用于从驱动到用户空间进程(运行在用户态下的应用)评测整个系统。这帮助开发人员找到性能问题

parisc - 这些驱动用于HP生产的PA-RISC架构设备。PA-RISC是一种特殊指令集的处理器。

parport - 并口驱动提供了Linux下的并口支持。

pci - 这些驱动提供了PCI总线服务。

pcmcia - 这些是笔记本的pc卡驱动

pinctrl - 这些驱动用来处理引脚控制设备。引脚控制器可以禁用或启用I/O设备。

platform -这个文件夹包含了不同的计算机平台的驱动像Acer、Dell、Toshiba、IBM、Intel、Chrombooks等等。

pnp - 即插即用驱动允许用户在插入一个像USB的设备后可以立即使用而不必手动配置设备。

power - 电源驱动使内核可以测量电池电量,检测充电器和进行电源管理。

pps - Pulse-Per-Second驱动用来控制电流脉冲速率。这用于计时。

ps3 - 这是Sony的游戏控制台驱动- PlayStation3。

ptp - 图片传输协议(PTP)驱动支持一种从数码相机中传输图片的协议。

pwm - 脉宽调制(PWM)驱动用于控制设备的电流脉冲。主要用于控制像CPU风扇。

rapidio - RapidIO驱动用于管理RapidIO架构,它是一种高性能分组交换,用于电路板上交互芯片的交互技术,也用于互相使用底板的电路板。

regulator - 校准驱动用于校准电流、温度、或其他可能系统存在的校准硬件。

remoteproc - 这些驱动用来管理远程处理器。

rpmsg - 这个驱动用来控制支持大量驱动的远程处理器通讯总线(rpmsg)。这些总线提供消息传递设施,促进客户端驱动程序编写自己的连接协议消息。

rtc - 实时时钟(RTC)驱动使内核可以读取时钟。

s390 - 用于31/32位的大型机架构的驱动。

sbus - 用于管理基于SPARC的总线驱动。

scsi - 允许内核使用SCSI标准外围设备。例如,Linux将在与SCSI硬件传输数据时使用SCSI驱动。
sfi -简单固件接口(SFI)驱动允许固件发送信息表给操作系统。这些表的数据称为SFI表。

sh - 该驱动用于支持SuperHway总线。

sn - 该驱动用于支持IOC3串口。

spi - 这些驱动处理串行设备接口总线(SPI),它是一个在在全双工下运行的同步串行数据链路标准,。全双工是指两个设备可以同一时间同时发送和接收信息。双工指的是双向通信。设备在主/从模式下通信(取决于设备配置)。

ssb - ssb(Sonics Silicon Backplane)驱动提供对在不同博通芯片和嵌入式设备上使用的迷你总线的支持。

staging - 该目录含有许多子目录。这里所有的驱动还需要在加入主内核前经过更多的开发工作。

target - SCSI设备驱动

tc - 这些驱动用于TURBOchannel,TURBOchannel是数字设备公司开发的32位开放总线。这主要用于DEC工作站。

thermal - thermal驱动使CPU保持较低温度。

tty - tty驱动用于管理物理终端连接。

uio - 该驱动允许用户编译运行在用户空间而不是内核空间的驱动。这使用户驱动不会导致内核崩溃。

usb - USB设备允许内核使用USB端口。闪存驱动和记忆卡已经包含了固件和控制器,所以这些驱动程序允许内核使用USB接口和与USB设备。

uwb - Ultra-WideBand驱动用来管理短距离,高带宽通信的超低功耗的射频设备

vfio - 允许设备访问用户空间的VFIO驱动。

vhost - 这是用于宿主内核中的virtio服务器驱动。用于虚拟化中。

video - 这是用来管理显卡和监视器的视频驱动。

virt - 这些驱动用来虚拟化。

virtio - 这个驱动用来在虚拟PCI设备上使用virtio设备。用于虚拟化中。

vlynq - 这个驱动控制着由德州仪器开发的专有接口。这些都是宽带产品,像WLAN和调制解调器,VOIP处理器,音频和数字媒体信号处理芯片。

vme - WMEbus最初是为摩托罗拉68000系列处理器开发的总线标准
w1 - 这些驱动用来控制one-wire总线。

watchdog - 该驱动管理看门狗定时器,这是一个可以用来检测和恢复异常的定时器。

xen - 该驱动是Xen管理程序系统。这是个允许用户运行多个操作系统在一台计算机的软件或硬件。这意味着xen的代码将允许用户在同一时间的一台计算机上运行两个或更多的Linux系统。用户也可以在Linux上运行Windows、Solaris、FreeBSD、或其他操作系统。

zorro - 该驱动提供Zorro Amiga总线支持。

参考syzkaller

GCC

安装GCC

Kernel

下载Linux内核,然后生成默认的配置选项

cd $KERNEL
make CC="$GCC/bin/gcc" defconfig
make CC="$GCC/bin/gcc" kvmconfig

手动编辑.config文件

CONFIG_KCOV=y
CONFIG_DEBUG_INFO=y
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y

对于较新版本的内核,还需要开启以下选项

CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

运行以下指令并按enter默认配置

make CC="$GCC/bin/gcc" oldconfig

编译内核

make CC="$GCC/bin/gcc"

Image

安装debootstrap

sudo apt-get install debootstrap

创建Linux镜像

cd $IMAGE/
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
chmod +x create-image.sh
./create-image.sh

QEMU

安装QEMU并启动

qemu-system-x86_64 \
  -kernel $KERNEL/arch/x86/boot/bzImage \
  -append "console=ttyS0 root=/dev/sda debug earlyprintk=serial slub_debug=QUZ"\
  -hda $IMAGE/stretch.img \
  -net user,hostfwd=tcp::10021-:22 -net nic \
  -enable-kvm \
  -nographic \
  -m 2G \
  -smp 2 \
  -pidfile vm.pid \
  2>&1 | tee vm.log

然后在另一个终端中使用ssh登陆

ssh -i $IMAGE/stretch.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost

syzkaller

安装Go 1.11或Go 1.12,然后下载syzkaller

go get -u -d github.com/google/syzkaller/...
cd $HOME?/go/src/github.com/google/syzkaller/
make

然后创建一个配置文件

{
    "target": "linux/amd64",
    "http": "127.0.0.1:56741",
    "workdir": "$GOPATH/src/github.com/google/syzkaller/workdir",
    "kernel_obj": "$KERNEL",
    "image": "$IMAGE/stretch.img",
    "sshkey": "$IMAGE/stretch.id_rsa",
    "syzkaller": "$GOPATH/src/github.com/google/syzkaller",
    "procs": 8,
    "type": "qemu",
    "vm": {
        "count": 4,
        "kernel": "$KERNEL/arch/x86/boot/bzImage",
        "cpu": 2,
        "mem": 2048
    }
}

运行syzkaller

mkdir workdir
./bin/syz-manager -config=my.cfg

可以在浏览器中输入 127.0.0.1:56741 查看结果

##

当运行syzkaller的时候qemu出现了一个小错误:

error: failed to set MSR 0x480 to 0x0
kvm_put_msrs: Assertion `ret == cpu->kvm_msr_buf->nmsrs' failed.

查询得知是一个bug,需要打patch

diff --git a/target/i386/kvm.c b/target/i386/kvm.c
index bf1655645b..e8841dcdb9 100644
--- a/target/i386/kvm.c
+++ b/target/i386/kvm.c
@@ -2572,6 +2572,14 @@  static void kvm_msr_entry_add_vmx(X86CPU *cpu, FeatureWordArray f)
     uint64_t kvm_vmx_basic =
     kvm_arch_get_supported_msr_feature(kvm_state,
                                        MSR_IA32_VMX_BASIC);
+    if (!kvm_vmx_basic) {
+        /* If the kernel does't support VMX feature(nested=0 in kvm)
+         * and kvm_vmx_basic will be 0. This will set 0 value to
+         * MSR_IA32_VMX_BASIC MSR.
+         */
+        return;
+    }
+
     uint64_t kvm_vmx_misc =
     kvm_arch_get_supported_msr_feature(kvm_state,
                                        MSR_IA32_VMX_MISC);

重新编译qemu就好了

搞定了AOSP的存储问题,下面开始编译AOSP系统了。

Build Android 10

由于众所周知的原因,我们不能按照AOSP官网上的介绍来构建AOSP,所以我开始选择了tuna镜像。按照教程http://https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/

传统初始化方法

建立工作目录:

mkdir DIR
cd DIR

初始化仓库:

repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest

如果提示无法连接到 gerrit.googlesource.com,可以将如下内容复制到你的~/.bashrc里,并重启终端

export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'

同步源码树:

repo sync

在实际执行的过程中,我发现tuna似乎出了一点问题,同步特别慢,所以我又选择了下面的方式。

使用初始化包

下载https://mirrors.tuna.tsinghua.edu.cn/aosp-monthly/aosp-latest.tar,下载完成后记得根据 checksum.txt 的内容校验一下。

tar -xvf aosp-lastest.tar -C DIR_NAME
cd aosp
repo sync -l //仅checkout代码,不同步

此处需要注意的是,要不就不要同步代码,要不就同步完代码,不要同步一半就停止,容易造成编译上的问题。

终于成功地拿到了AOSP源码,下面进行编译。

然而在编译的过程中,编译到metalava时,始终会遇到OutOfMemory错误,是由于内存空间不足造成,一时没有解决方案。

解决方案:

  1. export _JAVA_OPTIONS="-Xmx6g"
  2. 修改交换空间大小
  3. 减小并发数量
  4. https://stackoom.com/question/3v7vy/Metalava%E9%94%99%E8%AF%AF-%E5%86%85%E5%AD%98%E4%B8%8D%E8%B6%B3%E9%94%99%E8%AF%AF-Java%E5%A0%86%E7%A9%BA%E9%97%B4

上述方案都不能解决问题,只得放弃

Build Android 9

放弃编译版本10,只得退回到版本9。

然而现在不能使用初始化包了,所以我又换成了中科大源。

具体来说只需要修改为:

repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b android-9.0.0_r44

科大源还是非常快的,大概3个小时就完成了所有的同步工作。

之后就可以按照谷歌官方教程进行了

source build/envsetup.sh

这个过程引入了一些命令,下面要使用

lunch aosp_arm-eng //lunch后面的配置根据需要修改
m -j4 //进行编译

大概4个小时的时间,就完成编译了!

概述

最近在弄Android相关的东西,需要安装AOSP,然而AOSP需要占用的内存空间太大,原来给Ubuntu分配的空间根本不够用,重装系统来分配空间又太过麻烦,所以想探索一下动态重新分配硬盘空间的方法。

Windows

根据AOSP官网的描述,需要至少400G的空间,所以自然需要减少Windows的硬盘空间了。开始我使用了Windows自带的磁盘管理工具,但是效果不太好,功能极为有限,所以我改为使用Disk Genius软件,可以更改磁盘的大小,挪动空闲空间到Ubuntu系统附近。

Ubuntu

Disk Genius软件对于非Win系统的支持不太好,所以需要Gparted软件来进行Ubuntu系统的操作。在Ubuntu系统下输入

sudo apt-get install gparted

安装gparted,发现Ubuntu磁盘空间前面都有一个小钥匙,表示当前磁盘正在使用,不能更改大小。上网查询得知,需制作Ubuntu启动盘,从启动盘中试用Ubuntu,这时才能对磁盘空间进行操作。需要注意的是,Linux交换空间仍需要先停用才能修改大小。

因为更改涉及了根目录,对系统的启动有影响,所以需要重新安装grub。按照gparted官网的方法,进行如下操作:

  1. 从启动盘中进入Ubuntu系统,并打开一个终端。

  2. 弄清根目录所在盘符

  3. 创建一个挂载点

    sudo mkdir /tmp/mydir

  4. 将根目录挂载到挂载点上

    sudo mount /dev/sdb5 /tmp/mydir

  5. 如果有单独的/boot目录,则一同挂载

    sudo mount /dev/sdb6 /tmp/mydir/boot

    本机的情况比较特殊,还有单独的efi目录,也需要同时挂载,否则不能安装grub

    sudo mount /dev/sda3 /tmp/mydir/boot/efi

  6. 准备改变环境

    sudo mount --bind /dev /tmp/mydir/dev
    sudo mount --bind /proc /tmp/mydir/proc
    sudo mount --bind /sys /tmp/mydir/sys

  7. 改变环境

    sudo chroot /tmp/mydir

  8. 重新安装grub到根目录所在盘符(注意没有小盘符)

    sudo grub-install /dev/sdb

  9. 退出并重启

    exit

  10. 重启后发现grub找不到windows启动项,使用

    sudo update-grub

以上过程就完成了整个改变硬盘空间的过程,Ubuntu系统拥有了足够的空间安装AOSP。

第十三章 USB驱动程序

一个USB子系统并不是以总线的方式来布置的;它是一棵由几个点对点的连接构建而成的树。USB主控制器负责询问每一个USB设备是否有数据需要发送。

Linux内核主要支持两种主要类型的USB驱动程序:宿主系统上的驱动程序和设备上的驱动程序。从宿主的观点来看,宿主系统的USB驱动程序控制插入其中的USB设备,而USB设备的驱动程序控制该设备如何作为一个USB设备和主机通信。

USB设备基础

端点

SUB通信最基本的形式是通过一个名为端点的东西。USB端点只能往一个方向传送数据,端点可以看做单向的管道。

USB端点有四种不同的类型,分别具有不同的传送数据的方式:控制、中断、批量、等时

内核中使用struct usb_host_endpoint结构体来描述USB端点。该结构体在另一个名为struct usb_endpoint_descriptor的结构体中包含了真正的端点信息。后一个结构体包含了所有的USB特定的数据。该结构体中驱动程序需要关心的字段有:bEndpointAddress、bmAttributes、wMaxPacketSize、bInterval

接口

USB端点被捆绑为接口。USB接口只处理一种USB逻辑连接。一些USB设备具有多个接口,一个USB接口代表了一个基本功能,而每个USB驱动程序控制一个接口。

内核使用struct usb_interface结构体来描述USB接口。USB核心把该结构体传递给USB驱动程序,之后由USB驱动程序来负责控制该结构体。该结构体中的重要字段有:struct usb_host_interface *altsetting、 unsigned num_altsetting、struct usb_host_interface *cur_altsetting、int minor、struct usb_interface

配置

USB接口本身被捆绑为配置。一个USB设备可以有多个配置,而且可以在配置之间切换以改变设备的状态。

Linux使用struct usb_host_config结构体来描述USB配置,使用struct usb_device结构体来描述整个USB设备。

USB驱动程序通常需要把一个给定的struct usb_interface结构体的数据转换为一个struct usb_device结构体。

USB设备是非常复杂的,它由许多不同的逻辑单元组成。这些逻辑单元之间的关系可以简单地描述如下:

  • 设备通常具有一个或者更多的配置
  • 配置经常具有一个或者更多的接口
  • 接口通常具有一个或者更多的设置
  • 接口没有或者具有一个以上的端点

USB和Sysfs

第一个USB设备是一个根集线器。这是一个USB控制器,通常包含在一个PCI设备中。该控制器是连接PCI总线和USB总线的桥,也是该总线上的第一个USB设备。所有的根集线器都由USB核心分配了一个独特的编号。

概言之,USB sysfs设备命名方案为:

根集线器-集线器端口号:配置.接口

USB urb

Linux内核中的USB代码通过一个称为urb的东西和所有的USB设备通信。这个请求块使用struct urb结构体描述,可以从include/linux/usb.h文件中找到。

一个urb的典型生命周期如下:

  • 由USB设备驱动程序创建
  • 分配给一个特定USB设备的特定端点
  • 由USB设备驱动程序递交到USB核心
  • 由USB核心递交到特定设备的特定USB主控制器驱动程序
  • 由USB主控制器驱动程序处理,它从设备进行USB传送
  • 当urb结束之后,USB主控制器驱动程序通知USB设备驱动程序

编写USB驱动程序

注册USB驱动程序

所有USB驱动程序都必须创建的主要结构体是struct usb_driver。该结构体必须由USB驱动程序来填写,包括许多回调函数和变量,它们向USB核心代码描述了USB驱动程序。

static int __int usb_skel_init(void) {
    int result;

    result = usb_register(&skel_driver);
    if (result) {
        err("usb_register failed. Error number %d", result);
    }

    return result;
}

static void __exit usb_skel_exit(void) {
    usb_deregister(&skel_driver);
}

提交和控制urb

不使用urb的USB传输445