memset(addr , 0 , size) 导致Bus error问题分析

导致问题出现的demo代码

#define SH_DEV_MEM "/dev/mem"
#define myerror printf
// 获取由mmap映射的设备物理内存
static void *mymmap(u32 offset, u32 size, u8 is_rd_only, u8 is_clear)
{
        void *ptr;
        s32 fd;
        offset = 0x45E00000;
        size = 0x1000;
        is_rd_only = 0;
        is_clear = 1;

        /* open the shared memory object */
        // SH_DEV_MEM is "/dev/mem"
        if (is_rd_only)
                fd = open(SH_DEV_MEM, O_RDONLY | O_SYNC);
        else
                fd = open(SH_DEV_MEM, O_RDWR | O_SYNC);
        if (fd == -1)
        {
                myerror("Open %s: %s\n", SH_DEV_MEM, strerror(errno));
                return NULL;
        }

        // get a pointer to a piece of the shared memory, note that we only map in the amount we need to
        // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
        if (is_rd_only)
                ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
        else
                ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);

问题现象

在用户态通过mmap映射一段物理地址到用户空间,执行memset(ptr , 0, 0x100)时会导致Bus error:

...
fd = open("/dev/mem", O_RDONLY | O_SYNC);
ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x45e00000);
memset(ptr , 0, 0x100);

在这里插入图片描述
但以下情况不会:

  1. memset(ptr , val , size),如果val非0,不会出现Bus error
  2. memset(ptr , val , size),如果val为0,size<0x100,不会出现Bus error
  3. 0x45e00000地址如果添加到memory节点,不会出现Bus error
    在这里插入图片描述

问题分析

通过gdb分析,发现程序停在dc zva指令
在这里插入图片描述
查看arm v8官方文档发现,如果memory的类型是device type,执行dc zva时会有对齐错误产生,
(ps:dc zva cache zero by vir address)
在这里插入图片描述
在内核加打印追踪,发现确实产生了alignment fault
在这里插入图片描述
然后分析/driver/char/mem.c驱动,在mmap的时候,会设置内存的属性,当mmap的物理地址在memblock memory范围内,会设置内存属性为PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN),即normal memory,如果不在memblock memory范围,则设置为PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN),即device memory,这解释了,为什么0x45e00000添加到memory节点则不会出现Bus error的原因

static int mmap_mem(struct file *file, struct vm_area_struct *vma)
{
    size_t size = vma->vm_end - vma->vm_start;
    phys_addr_t offset = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT;

    /* It's illegal to wrap around the end of the physical address space. */
    if (offset + (phys_addr_t)size - 1 < offset)
        return -EINVAL;

    if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size))
        return -EINVAL;

    if (!private_mapping_ok(vma))
        return -ENOSYS;

    if (!range_is_allowed(vma->vm_pgoff, size))
        return -EPERM;

    if (!phys_mem_access_prot_allowed(file, vma->vm_pgoff, size,
                        &vma->vm_page_prot))
        return -EINVAL;

    vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
                         size,
                         vma->vm_page_prot);

    vma->vm_ops = &mmap_mem_ops;

    /* Remap-pfn-range will mark the range VM_IO */
    if (remap_pfn_range(vma,
                vma->vm_start,
                vma->vm_pgoff,
                size,
                vma->vm_page_prot)) {
        return -EAGAIN;
    }
    return 0;
}

// arch/arm64/mm/mmu.c
pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn,
                  unsigned long size, pgprot_t vma_prot)
{
    if (!pfn_valid(pfn)){
        return pgprot_noncached(vma_prot);
    }
    else if (file->f_flags & O_SYNC)
    {
        return pgprot_writecombine(vma_prot);
    }
    return vma_prot;
}
EXPORT_SYMBOL(phys_mem_access_prot);

#define pgprot_noncached(prot) \
    __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN)
#define pgprot_writecombine(prot) \
    __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN)
#define pgprot_device(prot) \
    __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN)
#define __HAVE_PHYS_MEM_ACCESS_PROT

int pfn_valid(unsigned long pfn)                                                                                                                               
{                                                                               
    return (pfn & PFN_MASK) == pfn && memblock_is_map_memory(pfn << PAGE_SHIFT);
}                                                                               
EXPORT_SYMBOL(pfn_valid); 

考虑到val为非0,不会出现Bus error,怀疑是glibc的memset,对val为0而且cout > 0x100的操作做了特殊的处理,查看glibc的源码,发现如果set的val,如果等于0,确实会做不同的操作,执行了dc zva指令,这解释了为什么非0的val会没问题,且size<0x100,也不会出现Bus error

// sysdeps/aarch64/memset.S
ENTRY_ALIGN (MEMSET, 6)

    DELOUSE (0)
    DELOUSE (2)

    dup v0.16B, valw
    add dstend, dstin, count

    cmp count, 96
    b.hi    L(set_long)
    cmp count, 16
    b.hs    L(set_medium)
    mov val, v0.D[0]
    
...

L(set_long):
    and valw, valw, 255
    bic dst, dstin, 15
    str q0, [dstin]
    cmp count, 256//
    ccmp valw, 0, 0, cs //cout > 0x100,并且val是0,则会使用zva指令,
    b.eq L(try_zva)
L(no_zva):
    sub count, dstend, dst  /* Count is 16 too large.  */
    sub dst, dst, 16        /* Dst is biased by -32.  */
    sub count, count, 64 + 16   /* Adjust count and bias for loop.  */
1:  stp q0, q0, [dst, 32]
    stp q0, q0, [dst, 64]!
L(tail64):
    subs    count, count, 64
    b.hi    1b
2:  stp q0, q0, [dstend, -64]
    stp q0, q0, [dstend, -32]
    ret

L(try_zva):
#ifdef ZVA_MACRO
    zva_macro
#else
    .p2align 3
    mrs tmp1, dczid_el0
    tbnz    tmp1w, 4, L(no_zva)
    and tmp1w, tmp1w, 15
    cmp tmp1w, 4    /* ZVA size is 64 bytes.  */
    b.ne     L(zva_128)

    /* Write the first and last 64 byte aligned block using stp rather
       than using DC ZVA.  This is faster on some cores.
     */
L(zva_64):
    str q0, [dst, 16]
    stp q0, q0, [dst, 32]
    bic dst, dst, 63
    stp q0, q0, [dst, 64]
    stp q0, q0, [dst, 96]
    sub count, dstend, dst  /* Count is now 128 too large.  */
    sub count, count, 128+64+64 /* Adjust count and bias for loop.  */
    add dst, dst, 128
    nop
1:  dc  zva, dst
    add dst, dst, 64
    subs    count, count, 64
    b.hi    1b
    stp q0, q0, [dst, 0]
    stp q0, q0, [dst, 32]
    stp q0, q0, [dstend, -64]
    stp q0, q0, [dstend, -32]
    ret

    .p2align 3
L(zva_128):
    cmp tmp1w, 5    /* ZVA size is 128 bytes.  */
    b.ne    L(zva_other)

    str q0, [dst, 16]
    stp q0, q0, [dst, 32]
    stp q0, q0, [dst, 64]
    stp q0, q0, [dst, 96]
    bic dst, dst, 127
    sub count, dstend, dst  /* Count is now 128 too large.  */
    sub count, count, 128+128   /* Adjust count and bias for loop.  */
    add dst, dst, 128
1:  dc  zva, dst
    add dst, dst, 128
    subs    count, count, 128
    b.hi    1b
    stp q0, q0, [dstend, -128]
    stp q0, q0, [dstend, -96]
    stp q0, q0, [dstend, -64]
    stp q0, q0, [dstend, -32]
    ret
...

#endif
END (MEMSET)

处理建议

把需要mmap的那段内存添加到memory和reserved-memory节点,以0x45E00000例:

    memory@50000000 {
        device_type = "memory";
        reg = <HIGH32(AP1_REE_MEMBASE) LOW32(AP1_REE_MEMBASE) HIGH32(AP1_REE_MEMSIZE) LOW32(AP1_REE_MEMSIZE) \
               HIGH32(AP1_2ND_MEMBASE) LOW32(AP1_2ND_MEMBASE) HIGH32(AP1_2ND_MEMSIZE) LOW32(AP1_2ND_MEMSIZE) \
               0x0 (VDSP_MEMBASE+0x4000) 0x0 (VDSP_SHARE_MEMSIZE + VDSP_MEMSIZE -0x4000)
               0x0 LOW32(0x45E00000) 0 0x200000>;
    };

    reserved-memory {
        #address-cells = <2>;
        #size-cells = <2>;
        ranges;

        mmap_test {
            /* reserve mem for mmap*/
            compatible = "mmap_test";
            //no-map;//注意不能用no-map标志
            reg = <0x0 LOW32(0x45E00000) 0 0x100000>;
        };
    };
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值