使用BFD操作ELF

2023-11-15

使用BFD操作ELF


创建时间:2001-09-21
文章属性:原创
文章来源: http://www.xfocus.org/
文章提交: alert7  (sztcww_at_sina.com)

使用BFD操作ELF


作者:alert7 <mailto: alert7@21cn.com 
                     alert7@xfocus.org
             >

主页:  http://www.xfocus.org
时间: 2001-9-21


★1. 前言 

BFD是Binary File Descriptor的简称。我们可以使用它来方便的操作应用程序,
可以在你不了解程序文件格式的情况下,读写ELF header,program header 
table,section header table还有各个section等等。当然也可以是其他的BFD
支持的object文件(比如说是COFF,a.out等等)。如果想让BFD支持新的文件格式的话,
只需把一个新的BFD的后端加到它的库里。

对每一个文件格式来说,BFD都分两个部分:一个前端(front end)和一个后端(back ends)。

.BFD的前端给用户提供接口。它管理内存和规范数据结构。前端也决定了哪个后端被使用
  和什么时候后端的例程被调用。
.BFD的前端维持了它的语言形式。后端也可能是它们自己使用的信息。


★2. 如何使用BFD

为了使用BFD,需要包括'bfd.h'并且连接的时候需要和静态库'libbfd.a'或者
动态库'libbfd.so'一起连接。

下面看一个简单的例子:

[alert7@redhat62 elf]$ cat test1.c

/*test1.c only for alert7 test*/
#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>
#include <strings.h>
#include <linux/elf.h>

#define nonfatal(s) {perror(s); return;}
#define fatal(s) {perror(s); exit(-1);}
#define bfd_nonfatal(s) {bfd_perror(s); return;}
#define bfd_fatal(s) {bfd_perror(s); exit(-1);}

main(int argc, char *argv[])  
{
      bfd *ibfd;
    char *filename;
    char **matching;

    if (argc<2) exit(-1);
    filename = argv[1];
    
    bfd_init();

      ibfd = bfd_openr(filename, NULL);
      if (ibfd == NULL)
      {
          bfd_nonfatal("openr");
    }

      if (bfd_check_format_matches(ibfd, bfd_object, &matching))
      {
    int sections_count=bfd_count_sections(ibfd);
    printf("%s section count %d\n",argv[1],sections_count);

    } else {
              bfd_fatal("format_matches");
    }
bfd_close(ibfd);
}

[alert7@redhat62 elf]$ gcc -c test1.c
[alert7@redhat62 elf]$ gcc -o test1 test1.o -lbfd -liberty
[alert7@redhat62 elf]$ ./test1 /lib/ld-linux.so.2
/lib/ld-linux.so.2 section count 19


★3. BFD一些基本的函数

bfd_init(void)
初始化BFD。


bfd *bfd_openr(CONST char *filename, CONST char *target);
以目标target打开filename文件。返回指向bfd的指针。target由
Calls_bfd_find_target函数来解析。
如果错误发生,返回NULL,那些错误可能是bfd_error_no_memory, 
bfd_error_invalid_target or system_call error. 


boolean bfd_set_default_target (const char *name);
设置默认的target向量,用来查找一个匹配的BFD。


boolean bfd_check_format(bfd *abfd, bfd_format format);
确定是否是BFD支持的文件格式(例如是bfd_object, bfd_archive或者bfd_core之一). 
假如在该调用之前已经设置了特别的target,那么仅仅和target相关联的格式被检查。
假如target没有被设置或者被设置为默认的,所有已知的后端将被询问是否跟他们匹配。


函数成功返回true,否则false,以下是一些错误代码:
bfd_error_invalid_operation - 假如格式不是bfd_object, bfd_archive和
        bfd_core之一。
bfd_error_system_call - 假如一个错误发生在读期间 - 甚至一些文件错误促发
        bfd_error_system_calls.
file_not_recognised - 没有后端可以认识该文件格式。
bfd_error_file_ambiguously_recognized - 超过一个的后端能认识该文件格式。


boolean bfd_check_format_matches(bfd *abfd, bfd_format format, 
                    char ***matching);
类似bfd_check_format, 除了当false返回时,bfd_errno被设置为
bfd_error_file_ambiguously_recognized.在这种情况下,假如matching不等于NULL,
它被填充为以NULL为结尾的一个已经注册了的所有的格式名称的列表。然后用户可以从中
选择一个格式再试一次。
当不需要时候,用户应该free指向列表指针的内存。


bfd *bfd_openw(CONST char *filename, CONST char *target);
使用格式的target,为文件filename创建一个BFD。返回指向BFD的指针。
错误可能是bfd_error_system_call, bfd_error_no_memory, bfd_error_invalid_target. 


boolean bfd_close(bfd *abfd);
Close BFD.假如以写打开的BFD,未完成的操作会被完成,然后文件才会被关闭。假如创建文件是
可执行的,将会调用chmod使它一致。所有BFD相关的内存被释放。
所有OK的话返回true,否则false.


bfd_get_symtab_upper_bound
返回所有标号需要的字节大小(也包括一个NULL指针)。假如没有标号,将返回为0。
错误返回-1。


bfd_canonicalize_symtab
从BFD中读标号。返回标号指针的个数(不包括NULL)。


boolean bfd_get_section_contents
   (bfd *abfd, asection *section, PTR location,
    file_ptr offset, bfd_size_type count);
把BFD abfd section中的数据读入内存location开始地址。数据从section的偏移量offset
开始读,count为要读数据的字节数。


boolean bfd_set_section_contents
   (bfd *abfd,
    asection *section,
    PTR data,
    file_ptr offset,
    bfd_size_type count);
把内存中data地址开始的数据设置为abfd BFD的节section的内容,数据从section的偏移量
offset开始写,count为要写的数据字节数。


void bfd_map_over_sections(bfd *abfd,
    void (*func)(bfd *abfd,
    asection *sect,
    PTR obj),
    PTR obj);
为BFD abfd相关的每个section调用私有的函数func,该函数将会被如下调用:
       func(abfd, the_section, obj);
该函数看起来就象执行了如下代码:
          section *p;
          for (p = abfd->sections; p != NULL; p = p->next)
             func(abfd, p, ...)


更多的bfd的库函数说明可以在以下找到:
http://www.gnu.org/manual/bfd-2.9.1/html_node/bfd_toc.html


★4. 通过BFD读符号表中符号

/*test2.c only for alert7 test*/
#include <stdio.h>
#include <stdlib.h>
#include <bfd.h>
#include <strings.h>
#include <linux/elf.h>

#define nonfatal(s) {perror(s); return;}
#define fatal(s) {perror(s); exit(-1);}
#define bfd_nonfatal(s) {bfd_perror(s); return;}
#define bfd_fatal(s) {bfd_perror(s); exit(-1);}

main(int argc, char *argv[])
{
        bfd *ibfd;
        char *filename;
        char **matching;

        if (argc<2) exit(-1);
        filename = argv[1];

        bfd_init();

        ibfd = bfd_openr(filename, NULL);
        if (ibfd == NULL)
        {
                bfd_nonfatal("openr");
        }

        if (bfd_check_format_matches(ibfd, bfd_object, &matching))
        {

        long storage_needed;
         asymbol **symbol_table;
         long number_of_symbols;
         long i;
         symbol_info symbolinfo ;

         storage_needed = bfd_get_symtab_upper_bound (ibfd);

         if (storage_needed < 0)
           bfd_nonfatal("bfd_get_symtab_upper_bound");

         if (storage_needed == 0) {
            return ;
         }
         symbol_table = (asymbol **) xmalloc (storage_needed);
        number_of_symbols =
            bfd_canonicalize_symtab (ibfd, symbol_table);

         if (number_of_symbols < 0)
           bfd_nonfatal("bfd_canonicalize_symtab");

        printf("Scanning %i symbols\n", number_of_symbols);
        for(i=0;i<number_of_symbols;i++)
                {
                if (symbol_table[i]->section==NULL) continue;
                printf("Section %s  ",symbol_table[i]->section->name);
                bfd_symbol_info(symbol_table[i],&symbolinfo);
                printf("Symbol \"%s\"  value 0x%x\n",
                         symbolinfo.name, symbolinfo.value);
                }


        } else {
                bfd_fatal("format_matches");
        }
bfd_close(ibfd);
}
/* EOF */


以上小程序打印符号的name和value还有相关的section.

[alert7@redhat62 elf]$ gcc -c test2.c
[alert7@redhat62 elf]$ gcc -o test2 test2.o -lbfd -liberty
[alert7@redhat62 elf]$ ./test2 helo
Scanning 75 symbols
Section .interp  Symbol ""  value 0x80480f4
Section .note.ABI-tag  Symbol ""  value 0x8048108
Section .hash  Symbol ""  value 0x8048128
......
Section *ABS*  Symbol "initfini.c"  value 0x0
Section .text  Symbol "gcc2_compiled."  value 0x804841c
Section *ABS*  Symbol "helo.c"  value 0x0
......
Section *ABS*  Symbol "_end"  value 0x8049540
Section .rodata  Symbol "_IO_stdin_used"  value 0x804843c
Section .data  Symbol "__data_start"  value 0x8049448
Section *UND*  Symbol "__gmon_start__"  value 0x0


再看看objdump输出什么
[alert7@redhat62 elf]$ objdump -t helo

helo:     file format elf32-i386

SYMBOL TABLE:
080480f4 l    d  .interp        00000000
08048108 l    d  .note.ABI-tag  00000000
08048128 l    d  .hash  00000000
......
00000000 l    df *ABS*  00000000              initfini.c
08048344 l       .text  00000000              gcc2_compiled.
00000000 l    df *ABS*  00000000              init.c
......
08049540 g     O *ABS*  00000000              _end
0804843c g     O .rodata        00000004              _IO_stdin_used
08049448 g       .data  00000000              __data_start
00000000  w      *UND*  00000000              __gmon_start__

(输出太多了,占太多大家宝贵的硬盘空间不好,所以只保留了以上输出)

其实objdump也是使用BFD的
[alert7@redhat62 elf]$ ldd /usr/bin/objdump
        libopcodes-2.9.5.0.22.so => /usr/lib/libopcodes-2.9.5.0.22.so (0x4001c000)
        libbfd-2.9.5.0.22.so => /usr/lib/libbfd-2.9.5.0.22.so (0x40032000)
        libdl.so.2 => /lib/libdl.so.2 (0x40077000)
        libc.so.6 => /lib/libc.so.6 (0x4007b000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)


★5. 写标号

演示如何进行写操作

/*test3.c for just show wirte operation*/
#include <stdio.h>
#include <bfd.h>
       main()
       {
         bfd *abfd;
         asymbol *ptrs[2];
         asymbol *new;

         abfd = bfd_openw("foo",NULL);
     if (abfd==NULL) exit(0);
         bfd_set_format(abfd, bfd_object);
         new = bfd_make_empty_symbol(abfd);
         new->name = "dummy_symbol";
         new->section = bfd_make_section_old_way(abfd, ".text");
         new->flags = BSF_GLOBAL;
         new->value = 0x12345;

         ptrs[0] = new;
         ptrs[1] = (asymbol *)0;

         bfd_set_symtab(abfd, ptrs, 1);
         bfd_close(abfd);
       }

[alert7@redhat62 elf]$ gcc -c test3.c
[alert7@redhat62 elf]$ gcc -o test3 test3.o -lbfd -liberty
[alert7@redhat62 elf]$ ./test3
[alert7@redhat62 elf]$ ls -ls foo
   4 -rw-rw-r--    1 alert7   alert7        350 Sep 21 09:56 foo
[alert7@redhat62 elf]$ file foo
foo: ELF 32-bit LSB relocatable, no machine, version 1, not stripped
[alert7@redhat62 elf]$ nm  foo
00012345 T dummy_symbol


★ 后记

本文只用来抛砖引玉,如果想用BFD写个实际有用的程序(比如实现绿盟月刊第14期上有篇
wangdb写的《如何修改动态库符号表》上的功能),需要自己好好研究研究那些BFD的库函数。
No pain,No gain...
不过虽然BFD功能很强大,但毕竟是别人封装过的,所以或许在自己琢磨那些库函数的时候,
就已经头晕了,而且不灵活,很不爽。我就是这样啦:(
封装了的库函数会屏蔽底层操作的细节,追求技术的你还是不要用它吧,但这就需要一些
ELF的知识。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用BFD操作ELF 的相关文章

  • 是什么让GDB拒绝崩溃?

    我在这里不知所措 我正在用 C 编写一个编译器 出于爱好 并使用 GDB 7 3 在 amd64 Linux 2 6 32 上使用 GCC 4 6 1 进行编译 除了通常的 I 等之外 标志还有 Wall Wextra O0 g 我有一个函
  • GDB - 如何打破“有些东西被写入cout”?

    我想设置一个断点 每次写入内容时都会触发stdout通过cout流 但我无法找到该断点的可能位置 我怎样才能在 gdb 中做到这一点 这是一种依赖于平台的方式 如果您在 x86 64 上并使用 gcc 进行构建 则写入 std cout 会
  • 如何在 gdb 中打印长字符串的完整值?

    我想在 GDB 中打印 C 字符串的完整长度 默认情况下它是缩写的 如何强制 GDB 打印整个字符串 set print elements 0 来自GDB手册 https sourceware org gdb onlinedocs gdb
  • GDB可以杀死一个特定的线程吗?

    我正在运行一个应用程序 firefox 我想知道是否可以使用 GDB 附加到进程并杀死特定线程 有没有办法做到这一点 我知道此操作可能会使应用程序崩溃 EDIT 在此调试会话中 ps ax显示firefox pid是1328 gdb App
  • 如何在 gdb 上进行 grep 打印

    有没有办法在 gdb 中 grep 打印命令的输出 就我而言 我正在使用 gdb 调试核心转储 并且我正在调试的对象包含大量元素 我发现很难寻找匹配的属性 即 gdb print this grep
  • 分离Gdb而不恢复劣质

    Gdb 与任何其他程序一样 并不完美 我时不时会遇到导致当前 Gdb 实例无法使用的错误 此时 如果我有一个调试会话 其中有很多有价值的状态 我希望能够在其上启动一个新的 Gdb 会话 也就是说 分离 退出 Gdb 并启动一个新的 Gdb
  • 在 gdb 中设置应用程序关联

    有没有一种简单的方法可以设置我正在调试的应用程序的亲和力 而无需将 gdb 锁定到同一核心 我问的原因是应用程序以实时优先级运行 并且需要在单核上运行 目前我使用这个命令行 taskset c 3 gdbserver 1234 app ou
  • GNU gdb 如何显示源文件名和符号行

    当使用 GNU gdb 调试 c 进程时 list 命令将打印行但不告诉我文件名 设置断点可以显示我想要的所有行和文件信息 但我不想设置断点并且必须禁用或删除它 gdb b oyss funtion Breakpoint 13 at 0x8
  • 专门逐行调试

    我有一个用 Pascal 编写的脚本 我会以这种方式调试它 在每一行停止 转储内存中所有变量的值 然后转到下一行 是否可以使用 gdb 或其他 Linux 开源工具来完成此操作 使用选项编译文件 g fpc gpc g file pas R
  • 使用 gdb 调试反汇编库

    在Linux和Mac OS X中可以使用strapi和next来调试应用程序而无需调试信息 在 Mac OS X 上 gdb 显示在库内部调用的函数 尽管有时会在每个 stepi 指令中推进多个汇编程序指令 在 Linux 上 当我进入动态
  • Visual Studio Code,调试子进程不起作用

    我有这个确切的问题 https github com Microsoft vscode cpptools issues 511 https github com Microsoft vscode cpptools issues 511 但那
  • 观察点固定地址

    对于我当前的嵌入式应用程序 我尝试将 GDB 观察点放在固定的内存地址处 例如 我的应用程序更新以下地址 0x10793ad0 为了确定代码的哪一部分破坏了值 我尝试了 watch 0x10793ad0 即使 GDB 在此之后不会打印任何错
  • 防止GDB中的PLT(过程链接表)断点

    在最新版本的 GDB 中 在库函数调用上设置断点会导致多个实际断点 调用过程链接表 PLT 实际的函数调用 这意味着当调用库函数时 我们每次都会经历两次中断 在以前的 GDB 版本中 只会创建 2 因此您只能得到一次中断 那么问题来了 是否
  • 为什么 GDB 启动一个新的 shell 以及如何禁用此行为?

    我正在解决一个问题 即从 GDB 启动应用程序会导致符号查找错误 但从 shell 启动它却可以 事实证明 每当你从 GDB 中启动一个程序时 它都会启动一个新的 shell 从而覆盖我在启动 GDB 之前设置的所有环境变量 例如LD LI
  • gcc 中 -g 选项的作用是什么

    我看到很多关于 gdb 的教程要求在编译 c 程序时使用 g 选项 我无法理解 g 选项的实际作用 它使编译器将调试信息添加到生成的二进制文件中 此信息允许调试器将代码中的指令与源代码文件和行号相关联 拥有调试符号可以使某些类型的调试 例如
  • 在 C 程序中追踪数组越界访问/写入的推荐方法

    考虑用 C 语言编写一些不太明显的算法的实现 例如 让它成为递归快速排序 我在 K N King 的 C 编程 现代方法 第二版 书中找到了它 可以从here http knking com books c2 programs qsort
  • 如何将整个 GDB 会话转储到文件中,包括我输入的命令及其输出?

    在 bash 中 我可以使用script命令 它将 shell 上显示的所有内容转储到文件中 包括 键入的命令 PS1 line 命令的 stdout 和 stderr gdb 中的等效项是什么 我试着跑shell script从 GDB
  • 使用valgrind进行GDB远程调试

    如果我使用远程调试gdb我连接到gdbserver using target remote host 2345 如果我使用 valgrind 和 gdb 调试内存错误 以中断无效内存访问 我会使用 target remote vgdb 启动
  • GDB - 将地址映射到源代码中的行和列

    gcc 和 clang 都有选项 gcolumn info 描述如下 将位置列信息发出到 DWARF 调试信息中 而不仅仅是文件和行 我已经使用此选项编译了我的二进制文件 现在我有一些指令的地址 想将其翻译回源文件 行号和列 有没有办法在g
  • 使用 math.h 函数时 gdb 给出奇怪的输出[重复]

    这个问题在这里已经有答案了 可能的重复 为什么 gdb 将 sqrt 3 计算为 0 https stackoverflow com questions 5122570 why does gdb evaluate sqrt3 to 0 这里

随机推荐

  • 数据库如何提高大数据量查询速度

    数据库如何提高大数据量查询速度 1 对查询进行优化 应尽量避免全表扫描 首先应考虑在 where 及 order by 涉及的列上建立索引 2 应尽量避免在 where 子句中对字段进行 null 值判断 否则将导致引擎放弃使用索引而进行全
  • Android的GreenDao3.0数据库详解及使用

    GreenDao 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案 性能 官网上的解释 我们知道所有的ORM的 greenDAO是最快的 greenDAO不作性能方面任何妥协 数据库是非常适合存储大量数据 从而加速
  • Linux 文本处理工具

    一 Linux 及Unix平台 sed awk grep 这三个工具都要用到正则表达式 把常用贴出来 1 行的匹配 root mypc sed n 2p etc passwd 打印出第2行 root mypc sed n 1 3p etc
  • OSSEC服务端配置客户端批量部署方案

    hello 2015 10 16 15 03 0x00 前言 最近也在研究ossec报警规则 还没研究的很透彻 暂时不是这篇文章的内容 ossec中文资料还是比较少 外文文献比较多 之前看到drops的两篇文章分享drops wooyun
  • Python回归预测建模实战-支持向量机预测房价(附源码和实现效果)

    机器学习在预测方面的应用 根据预测值变量的类型可以分为分类问题 预测值是离散型 和回归问题 预测值是连续型 前面我们介绍了机器学习建模处理了分类问题 具体见之前的文章 接下来我们以波斯顿房价数据集为例 做一个回归预测系列的建模文章 实现功能
  • 分而治之-前端模块化

    Created By JishuBao on 2019 03 20 12 38 22 Recently revised in 2019 03 20 12 38 22 欢迎大家来到技术宝的掘金世界 您的star是我写文章最大的动力 GitHu
  • 电脑中病毒了怎么修复,计算机Windows系统预防faust勒索病毒方法

    随着计算机系统的不断发展 我们所面对的网络安全威胁也变得越来越严重 其中 较为常见且危险的威胁就是勒索病毒 随着勒索病毒加密算法的不断升级 最近faust勒索病毒开始流行 Faust勒索病毒主要的攻击目标是Windows操作系统 一旦我们的
  • 【Webserver】——线程池的原理,手写线程池

    目录 1 什么是线程池 2 线程池的作用 3 任务队列的设计 4 构造函数 5 push接口设计 6 子线程的执行函数 7 析构函数 8 测试线程池 9 线程池中的线程数量设定 1 经验值 2 最佳线程数目算法 1 什么是线程池 线程池是一
  • 如何在VMware Workstation上安装Linux系统

    目录 一 在VMware Workstation上新建Linux虚拟机 1 1 配置选择典型 1 2 选择稍后安装 1 3 选择Linux系统 版本选择Ubuntu 1 4 给虚拟机命名 一定要英文 1 5 给磁盘分配大小 将磁盘拆分 1
  • 基于OpenMP的质数并发求解方法研究

    并行程序设计 的结课论文 基于OpenMP的质数并发求解方法研究 摘要 如何快速地获得素数表以解决素数相关的复杂问题 具有重要的研究意义 给定范围内求解质数的串行算法主要有以下三种 枚举 埃氏筛 欧拉筛 本文研究给定范围内质数求解的并发性算
  • Nginx入门、下载安装启动(Win10)、常用配置

    文章目录 1 Nginx简介 2 下载安装启动 3 Nginx的常用基本配置 3 1 Nginx配置文件结构 3 2 设置用户和组 3 3 自定义错误页 1 Nginx简介 Nginx是一个轻量级开源Web服务器软件 可以作为反向代理 负载
  • 分子动力学模拟MD simulation需要注意的点有哪些

    一 GROMACS分子动力学蛋白模拟 药物开发溶剂筛选 1 分子模拟基础理论 1 1 统计力学理论概述 1 2 主要算法介绍 最速下降法 共轭梯度法 有限差分法 1 3 力场 力场类型 参数和分类 AMBER CHARMM MMX CVFF
  • 1.3 CSDN考试C1 奇偶校验

    文章目录 1 为什么数据校验 2 奇偶校验 3 练习题 3 1练习1 3 2练习2 1 为什么数据校验 数据在传输的过程中 会受到各种干扰的影响 如脉冲干扰 随机噪声干扰和人为干扰 等 这会使数据产生差错 为了能够控制 减少甚至消除传输过程
  • Linux下的硬件驱动——USB设备(下)(驱动开发部分)

    http www ibm com developerworks cn linux l usb index2 html Linux下的硬件驱动 USB设备 下 驱动开发部分 赵明 联想软件设计中心嵌入式研发处系统设计工程师 2003年7月 赵
  • python findall函数用法_Python--re模块的findall等用法

    1 正则表达式含义 点可代表一切字符 起转义作用 指代方括号中的任意字符 d 指代数字0 9 D 指代非数字 s 指代一切空格 包括tab制表符 空格 换行等 S 指代非空格 w 指代大小写字母 数字和下划线 W 指代非大小写字母 数字和下
  • 简易登录界面html+css(自学)

    页面展示 代码展示 html代码 图标使用阿里巴巴矢量图标库图标 阿里巴巴矢量图标库地址
  • 视频图像处理课程推荐(持续更新...)

    1 斯坦福大学 课程EE367 CS448I https web stanford edu class ee367 课程内容有 Introduction and fast forward overview of class logistic
  • GPIO的地址和寄存器映射

    1 GPIO详解 1 1 gpio框图 与GPIO相关的寄存器 不涉及复用 简单理解就是电灯 蜂鸣器控制等 与之相关的寄存器一共有7个 GPIOx CRL x A E 端口配置低寄存器 GPIOx CRH x A E 端口配置高寄存器 GP
  • 如何快速启动npm run build 后的dist文件呢?

    1 通过npm run build 打包后会出现如下 tips 提示我们打包完的项目 必须要在http server 下才能运行 2 安装http server 进入 dist 文件夹 然后启动一个http服务即可 或者 你现在已经到apa
  • 使用BFD操作ELF

    使用BFD操作ELF 创建时间 2001 09 21 文章属性 原创 文章来源 http www xfocus org 文章提交 alert7 sztcww at sina com 使用BFD操作ELF 作者 alert7