前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >mold源码阅读十二 创建一些输出段

mold源码阅读十二 创建一些输出段

作者头像
AkemiHomura
发布2023-10-22 16:31:29
1520
发布2023-10-22 16:31:29
举报
文章被收录于专栏:homura的博客homura的博客

Fill gnu.version section contents

代码语言:javascript
复制
// Fill .gnu.version_d section contents.
if (ctx.verdef)
  ctx.verdef->construct(ctx);

// Fill .gnu.version_r section contents.
ctx.verneed->construct(ctx);

这里对verdef和verneed段进行构造,实际写入内容。其中包含了字符串信息,因此还会将字符串写入dynstr中。

verdef

对于VerdefSection中的contents是多组ElfVerDef + ElfVerdaux。前者是verdef的信息,后者则是指向对应字符串在dynstr中的offset。

需要将ctx.arg.version_definitions以及output自身的信息写入到verdef段中,因此这样的数据实际有ctx.arg.version_definitions.size() + 1组。

代码语言:javascript
复制
| verdef | verdaux | verdef | verdaux |
						/                  /	
|     dynstr0    |    dynstr1    | ... | dynstrn |
代码语言:javascript
复制
template <typename E>
class VerdefSection : public Chunk<E> {
public:
  VerdefSection() {
    this->name = ".gnu.version_d";
    this->shdr.sh_type = SHT_GNU_VERDEF;
    this->shdr.sh_flags = SHF_ALLOC;
    this->shdr.sh_addralign = 8;
  }

  void construct(Context<E> &ctx);
  void update_shdr(Context<E> &ctx) override;
  void copy_buf(Context<E> &ctx) override;

  std::vector<u8> contents;
};

每次写入的时候会先在当前位置写入ElfVerDef的信息,之后写入ElfVerdaux的信息,同时在这个过程中更新当前位置的指针。传入的verstr实际保存在ctx.dynstr中,而Verdaux中保存的是则是verstr在dynstr中的offset,而VerDef仅保存索引,hash等信息。

代码语言:javascript
复制
template <typename E>
void VerdefSection<E>::construct(Context<E> &ctx) {
  Timer t(ctx, "fill_verdef");

  if (ctx.arg.version_definitions.empty())
    return;

  // Resize .gnu.version
  ctx.versym->contents.resize(ctx.dynsym->symbols.size(), 1);
  ctx.versym->contents[0] = 0;

  // Allocate a buffer for .gnu.version_d.
  contents.resize((sizeof(ElfVerdef<E>) + sizeof(ElfVerdaux<E>)) *
                  (ctx.arg.version_definitions.size() + 1));

  u8 *buf = (u8 *)&contents[0];
  u8 *ptr = buf;
  ElfVerdef<E> *verdef = nullptr;

  auto write = [&](std::string_view verstr, i64 idx, i64 flags) {
    this->shdr.sh_info++;
    if (verdef)
      verdef->vd_next = ptr - (u8 *)verdef;

    verdef = (ElfVerdef<E> *)ptr;
    ptr += sizeof(ElfVerdef<E>);

    verdef->vd_version = 1;
    verdef->vd_flags = flags;
    verdef->vd_ndx = idx;
    verdef->vd_cnt = 1;
    verdef->vd_hash = elf_hash(verstr);
    verdef->vd_aux = sizeof(ElfVerdef<E>);

    ElfVerdaux<E> *aux = (ElfVerdaux<E> *)ptr;
    ptr += sizeof(ElfVerdaux<E>);
    aux->vda_name = ctx.dynstr->add_string(verstr);
  };

  std::string_view basename = ctx.arg.soname.empty() ?
    ctx.arg.output : ctx.arg.soname;
  write(basename, 1, VER_FLG_BASE);

  i64 idx = 2;
  for (std::string_view verstr : ctx.arg.version_definitions)
    write(verstr, idx++, 0);

  for (Symbol<E> *sym : std::span<Symbol<E> *>(ctx.dynsym->symbols).subspan(1))
    ctx.versym->contents[sym->get_dynsym_idx(ctx)] = sym->ver_idx;
}

ver_idx的值是

代码语言:javascript
复制
static constexpr u32 VER_NDX_LOCAL = 0;
static constexpr u32 VER_NDX_GLOBAL = 1;
static constexpr u32 VER_NDX_LAST_RESERVED = 1;

verneed

这里的数据格式和vardef不太一样,content是一个Verneed接着多个Vednaux构成。每个Verneed表示一个文件的开始。由于这里是针对dynsym处理,因此实际Vednaux的数量和dynsym的数量相同。在分配空间的时候注释也有写到allocate large enought buffer,避免了每个文件一个dynsym的极端场景。

代码语言:javascript
复制
|verneed|vednaux|vednaux|...|verneed|vednaux|vednaux|vednaux|

另外不在dso或者sym->ver_idx <= VER_NDX_LAST_RESERVED的sym,这些符号并不需要填充verneed字段,因此会先被过滤掉。之后由于content是以一个文件为一个小组,为了后面添加信息方便会根据soname以及ver_idx进行排序。

代码语言:javascript
复制
template <typename E>
void VerneedSection<E>::construct(Context<E> &ctx) {
  Timer t(ctx, "fill_verneed");

  if (ctx.dynsym->symbols.empty())
    return;

  // Create a list of versioned symbols and sort by file and version.
  std::vector<Symbol<E> *> syms(ctx.dynsym->symbols.begin() + 1,
                                ctx.dynsym->symbols.end());

  std::erase_if(syms, [](Symbol<E> *sym) {
    return !sym->file->is_dso || sym->ver_idx <= VER_NDX_LAST_RESERVED;
  });

  if (syms.empty())
    return;

  sort(syms, [](Symbol<E> *a, Symbol<E> *b) {
    return std::tuple(((SharedFile<E> *)a->file)->soname, a->ver_idx) <
           std::tuple(((SharedFile<E> *)b->file)->soname, b->ver_idx);
  });

  // Resize of .gnu.version
  ctx.versym->contents.resize(ctx.dynsym->symbols.size(), 1);
  ctx.versym->contents[0] = 0;

  // Allocate a large enough buffer for .gnu.version_r.
  contents.resize((sizeof(ElfVerneed<E>) + sizeof(ElfVernaux<E>)) * syms.size());

  // Fill .gnu.version_r.
  u8 *buf = (u8 *)&contents[0];
  u8 *ptr = buf;
  ElfVerneed<E> *verneed = nullptr;
  ElfVernaux<E> *aux = nullptr;

  u16 veridx = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size();

  for (i64 i = 0; i < syms.size(); i++) {
    if (i == 0 || syms[i - 1]->file != syms[i]->file) {
      start_group(syms[i]->file);
      add_entry(syms[i]);
    } else if (syms[i - 1]->ver_idx != syms[i]->ver_idx) {
      add_entry(syms[i]);
    }

    ctx.versym->contents[syms[i]->get_dynsym_idx(ctx)] = veridx;
  }

  // Resize .gnu.version_r to fit to its contents.
  contents.resize(ptr - buf);
}

处理过程中根据如果是第一个符号或者连续两个符号不是相同的file就start_group。

要注意ctx.versym->contents又重新resize了一次,在后面遍历符号的时候又会再次写入,或许是因为verdef是根据选项来决定是否执行的。两次resize实际上size是相同的,而verneed中并非所有符号都会写入versym→content,部分被过滤的符号是没有再次写入的,也就是说被过滤的符号会保留verneed的部分。

接着来看一下start_group的部分。这个函数中会sh_info递增,处理verneed(关联一个file),并且aux置空。也就是说VerneedSection的sh_info存放的是ElfVerneed的数量。每个ElfVerneed关联了一个文件,以及aux的size。

代码语言:javascript
复制
auto start_group = [&](InputFile<E> *file) {
  this->shdr.sh_info++;
  if (verneed)
    verneed->vn_next = ptr - (u8 *)verneed;

  verneed = (ElfVerneed<E> *)ptr;
  ptr += sizeof(*verneed);
  verneed->vn_version = 1;
  verneed->vn_file = ctx.dynstr->find_string(((SharedFile<E> *)file)->soname);
  verneed->vn_aux = sizeof(ElfVerneed<E>);
  aux = nullptr;
};

在add_entry中会递增当前的verneed的计数,将信息填写到ElfVernaux中,并且更新当前aux的指针

代码语言:javascript
复制
auto add_entry = [&](Symbol<E> *sym) {
  verneed->vn_cnt++;

  if (aux)
    aux->vna_next = sizeof(ElfVernaux<E>);
  aux = (ElfVernaux<E> *)ptr;
  ptr += sizeof(*aux);

  std::string_view verstr = sym->get_version();
  aux->vna_hash = elf_hash(verstr);
  aux->vna_other = ++veridx;
  aux->vna_name = ctx.dynstr->add_string(verstr);
};

create_output_symtab

这个过程是用于创建symtab和strtab,创建的时候会实际选择哪些符号要写到文件中。我们熟悉的strip,如果添加了链接选项那么就是在这里开始生效的。

相关的链接选项在mold中有如下几个

-s, –strip-all Strip .symtab section –retain-symbols-file FILE Keep only symbols listed in FILE discard_all

strip大家都很熟悉了,就是去掉生成文件中的symtab段

retain-symbols-file则是会产生一个符号文件,包含程序的调试信息,也就是说生成的文件说不包含符号信息,所有符号都在符号文件中。

discard_all是丢弃目标程序中未直接使用的信息,其中就包含符号表和调试信息。

代码语言:javascript
复制
// Compute .symtab and .strtab sizes for each file.
create_output_symtab(ctx);
代码语言:javascript
复制
template <typename E>
void create_output_symtab(Context<E> &ctx) {
  Timer t(ctx, "compute_symtab_size");

  tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
    chunk->compute_symtab_size(ctx);
  });

  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    file->compute_symtab_size(ctx);
  });

  tbb::parallel_for_each(ctx.dsos, [&](SharedFile<E> *file) {
    file->compute_symtab_size(ctx);
  });
}

chunk

代码语言:javascript
复制
// Chunk::compute_symtab_size

// Some synethetic sections add local symbols to the output.
// For example, range extension thunks adds function_name@thunk
// symbol for each thunk entry. The following members are used
// for such synthesizing symbols.
virtual void compute_symtab_size(Context<E> &ctx) {};

对于chunk来说,不是所有的都需要做这一步操作的。在mold中仅针对OutputSection,Got,Plt,PltGot这几个chunk来处理。

实际要做的就是遍历所有符号更新其strtab_size以及num_local_symtab(用于标记local符号的数量,也就是这个阶段要计算的symtab size),不论是哪一种chunk都是如此,下面就不再赘述,只贴代码了。

OutputSection

代码语言:javascript
复制
// Compute spaces needed for thunk symbols
template <typename E>
void OutputSection<E>::compute_symtab_size(Context<E> &ctx) {
  if (ctx.arg.strip_all || ctx.arg.retain_symbols_file || ctx.arg.relocatable)
    return;

  if constexpr (needs_thunk<E>) {
    this->strtab_size = 0;
    this->num_local_symtab = 0;

    if constexpr (std::is_same_v<E, ARM32>)
      this->strtab_size = 9; // for "$t", "$a" and "$d" symbols

    for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : thunks) {
      // For ARM32, we emit additional symbol "$t", "$a" and "$d" for
      // each thunk to mark the beginning of ARM code.
      if constexpr (std::is_same_v<E, ARM32>)
        this->num_local_symtab += thunk->symbols.size() * 4;
      else
        this->num_local_symtab += thunk->symbols.size();

      for (Symbol<E> *sym : thunk->symbols)
        this->strtab_size += sym->name().size() + sizeof("$thunk");
    }
  }
}

注意这里relocatable的段也不会算入symtab size中,因为地址并非固定,需要加载时重定位,如果把符号放入输出文件中,会使得重定位更加困难,并且加载时会失效。

need_thunk:

  1. 输出段中代码间隔比较大,直接跳转无法到达的时候需要thunk来中专
  2. 跳转的src和dest指令集不兼容需要thunk翻译
  3. 地址随机化(ASLR: Address space layout randomization)时需要thunk动态计算目标地址
  4. 地址运行时才能确定时需要thunk计算地址

基本上都是一些无法直接跳转的情况,也因此会引入新的符号。而thunk本质上是一个新的代码段,需要符号进行表示,用以被其他代码识别。

代码语言:javascript
复制
template <typename E>
static constexpr bool needs_thunk = requires { E::thunk_size; };

根据mold代码中的实现,目前需要thunk的是ARM32,ARM64,PPC64V1,PPC64V2

got

代码语言:javascript
复制
template <typename E>
void GotSection<E>::compute_symtab_size(Context<E> &ctx) {
  if (ctx.arg.strip_all || ctx.arg.retain_symbols_file)
    return;

  this->strtab_size = 0;
  this->num_local_symtab = 0;

  for (Symbol<E> *sym : got_syms) {
    this->strtab_size += sym->name().size() + sizeof("$got");
    this->num_local_symtab++;
  }

  for (Symbol<E> *sym : gottp_syms) {
    this->strtab_size += sym->name().size() + sizeof("$gottp");
    this->num_local_symtab++;
  }

  for (Symbol<E> *sym : tlsgd_syms) {
    this->strtab_size += sym->name().size() + sizeof("$tlsgd");
    this->num_local_symtab++;
  }

  for (Symbol<E> *sym : tlsdesc_syms) {
    this->strtab_size += sym->name().size() + sizeof("$tlsdesc");
    this->num_local_symtab++;
  }

  if (tlsld_idx != -1) {
    this->strtab_size += sizeof("$tlsld");
    this->num_local_symtab++;
  }
}

PLT

代码语言:javascript
复制
template <typename E>
void PltSection<E>::compute_symtab_size(Context<E> &ctx) {
  if (ctx.arg.strip_all || ctx.arg.retain_symbols_file)
    return;

  this->num_local_symtab = symbols.size();
  this->strtab_size = 0;

  for (Symbol<E> *sym : symbols)
    this->strtab_size += sym->name().size() + sizeof("$plt");
}

PLTGOT

代码语言:javascript
复制
template <typename E>
void PltGotSection<E>::compute_symtab_size(Context<E> &ctx) {
  if (ctx.arg.strip_all || ctx.arg.retain_symbols_file)
    return;

  this->num_local_symtab = symbols.size();
  this->strtab_size = 0;

  for (Symbol<E> *sym : symbols)
    this->strtab_size += sym->name().size() + sizeof("$pltgot");
}

obj

在obj中,主要计算了local和global符号的名字占用的空间,用于更新strtable_size,另外还会更新对应的output_sym_indices

要注意的是计算名字空间的时候,这里的名字需要使用null结尾,因此size还需要加一。

代码语言:javascript
复制
template <typename E>
void ObjectFile<E>::compute_symtab_size(Context<E> &ctx) {
  if (ctx.arg.strip_all)
    return;

  this->output_sym_indices.resize(this->elf_syms.size(), -1);

  auto is_alive = [&](Symbol<E> &sym) -> bool {
    if (!ctx.arg.gc_sections)
      return true;

    if (SectionFragment<E> *frag = sym.get_frag())
      return frag->is_alive;
    if (InputSection<E> *isec = sym.get_input_section())
      return isec->is_alive;
    return true;
  };

  // Compute the size of local symbols
  if (!ctx.arg.discard_all && !ctx.arg.strip_all && !ctx.arg.retain_symbols_file) {
    for (i64 i = 1; i < this->first_global; i++) {
      Symbol<E> &sym = *this->symbols[i];

      if (is_alive(sym) && should_write_to_local_symtab(ctx, sym)) {
        this->strtab_size += sym.name().size() + 1;
        this->output_sym_indices[i] = this->num_local_symtab++;
        sym.write_to_symtab = true;
      }
    }
  }

  // Compute the size of global symbols.
  for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
    Symbol<E> &sym = *this->symbols[i];

    if (sym.file == this && is_alive(sym) &&
        (!ctx.arg.retain_symbols_file || sym.write_to_symtab)) {
      this->strtab_size += sym.name().size() + 1;
      // Global symbols can be demoted to local symbols based on visibility,
      // version scripts etc.
      if (sym.is_local(ctx))
        this->output_sym_indices[i] = this->num_local_symtab++;
      else
        this->output_sym_indices[i] = this->num_global_symtab++;
      sym.write_to_symtab = true;
    }
  }
}

对于local symbol除了要判断alive之外,还有一个should_write_to_local_symtab的判断,除了更新size外还会更新write_to_symtab

代码语言:javascript
复制
template <typename E>
static bool should_write_to_local_symtab(Context<E> &ctx, Symbol<E> &sym) {
  if (sym.get_type() == STT_SECTION)
    return false;

  // Local symbols are discarded if --discard-local is given or they
  // are in a mergeable section. I *believe* we exclude symbols in
  // mergeable sections because (1) there are too many and (2) they are
  // merged, so their origins shouldn't matter, but I don't really
  // know the rationale. Anyway, this is the behavior of the
  // traditional linkers.
  if (sym.name().starts_with(".L")) {
    if (ctx.arg.discard_locals)
      return false;

    if (InputSection<E> *isec = sym.get_input_section())
      if (isec->shdr().sh_flags & SHF_MERGE)
        return false;
  }

  return true;
}

-X, –discard-locals Discard temporary local symbols

本地符号以本地标签为前缀开头,这个标签通常为.L,这里主要是对discard_locals进行处理,另外属于SHF_MERGE的段也不会写到local,根据这里注释的意思是SHF_MERGE段段符号太多了,并且是merge以后的,所以其来源不重要,并且传统的链接器都是这么做的。(我对这块也不了解,只能按照注释所说的来看了)

还有一个sym.is_local的判断看起来比较疑惑。根据注释所描述,global sym会基于visibility和version scripts等因素变成local sym,比如说设置某个global sym的可见性为特定范围,或者对应的脚本。当全局符号降级为local的时候则不再对外可见,因此不再占用全局符号表的空间。

代码里的判断是这样的

代码语言:javascript
复制
template <typename E>
inline bool Symbol<E>::is_local(Context<E> &ctx) const {
  if (ctx.arg.relocatable)
    return esym().st_bind == STB_LOCAL;
  return !is_imported && !is_exported;
}

对于relocatable来说,如果st_bind为STB_LOCAL,那么这个符号一定是local的

对于非imported以及非exported的全局符号,通常是模块内部实现细节使用,不能外部访问。比如说有如下几种情况

  1. 静态全局符号,只能模块内部可见(因为静态符号的作用域限定在模块内,因此会被认为是local符号,对全局静态变量的访问只需要通过内存地址,而不需要符号名进行绑定)
  2. 匿名全局符号,没有被显示的使用export或者extern等进行标记,并且对外部是不可见的。比如说在.c中定义了一个全局变量,但是外部无法访问到。
  3. 未使用的全局符号,不会被访问,同时会被优化掉

因此这些情况属于local,记入num_local_symtab

关于imported和exported的计算过程,可以参考之前第五期的文章,其中有根据可见性来设置exported和imported的部分

https://cloud.tencent.com/developer/article/2277989

dso

代码语言:javascript
复制
template <typename E>
void SharedFile<E>::compute_symtab_size(Context<E> &ctx) {
  if (ctx.arg.strip_all)
    return;

  this->output_sym_indices.resize(this->elf_syms.size(), -1);

  // Compute the size of global symbols.
  for (i64 i = this->first_global; i < this->symbols.size(); i++) {
    Symbol<E> &sym = *this->symbols[i];

    if (sym.file == this && (sym.is_imported || sym.is_exported) &&
        (!ctx.arg.retain_symbols_file || sym.write_to_symtab)) {
      this->strtab_size += sym.name().size() + 1;
      this->output_sym_indices[i] = this->num_global_symtab++;
      sym.write_to_symtab = true;
    }
  }
}

这里的要点就是imported或者exported才需要计入num_global_symtab

eh_frame_construct

代码语言:javascript
复制
// .eh_frame is a special section from the linker's point of view,
// as its contents are parsed and reconstructed by the linker,
// unlike other sections that are regarded as opaque bytes.
// Here, we construct output .eh_frame contents.
ctx.eh_frame->construct(ctx);

由于eh_frame在mold中自行做了parse的,因此需要再手动构造output中eh_frame的部分,在构造的过程中主要是消除重复的部分,另外各个段是由offset以及idx关联起来的,更新这些信息也是必要的工作。

关于mold自行parse eh_frame的部分可以参考第二期的内容https://cloud.tencent.com/developer/article/2259909

在构造的过程主要由如下几部分组成

  1. 确保输入存在eh_frame,不存在则无需构造
  2. 删除dead fed,重新设置offset
  3. uniquify cie,重新设置offset
  4. fde idx的更新
  5. 重新设置文件中存储的fde的offset
  6. 填充最后的null word
代码语言:javascript
复制
template <typename E>
class EhFrameSection : public Chunk<E> {
public:
  EhFrameSection() {
    this->name = ".eh_frame";
    this->shdr.sh_type = SHT_PROGBITS;
    this->shdr.sh_flags = SHF_ALLOC;
    this->shdr.sh_addralign = sizeof(Word<E>);
  }

  void construct(Context<E> &ctx);
  void apply_reloc(Context<E> &ctx, const ElfRel<E> &rel, u64 offset, u64 val);
  void copy_buf(Context<E> &ctx) override;
};
代码语言:javascript
复制
template <typename E>
void EhFrameSection<E>::construct(Context<E> &ctx) {
  Timer t(ctx, "eh_frame");

  // If .eh_frame is missing in all input files, we don't want to
  // create an output .eh_frame section.
  if (std::all_of(ctx.objs.begin(), ctx.objs.end(),
                  [](ObjectFile<E> *file) { return file->cies.empty(); })) {
    this->shdr.sh_size = 0;
    return;
  }

  // Remove dead FDEs and assign them offsets within their corresponding
  // CIE group.
  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    std::erase_if(file->fdes, [](FdeRecord<E> &fde) { return !fde.is_alive; });

    i64 offset = 0;
    for (FdeRecord<E> &fde : file->fdes) {
      fde.output_offset = offset;
      offset += fde.size(*file);
    }
    file->fde_size = offset;
  });

  // Uniquify CIEs and assign offsets to them.
  std::vector<CieRecord<E> *> leaders;
  auto find_leader = [&](CieRecord<E> &cie) -> CieRecord<E> * {
    for (CieRecord<E> *leader : leaders)
      if (cie.equals(*leader))
        return leader;
    return nullptr;
  };

  i64 offset = 0;
  for (ObjectFile<E> *file : ctx.objs) {
    for (CieRecord<E> &cie : file->cies) {
      if (CieRecord<E> *leader = find_leader(cie)) {
        cie.output_offset = leader->output_offset;
      } else {
        cie.output_offset = offset;
        cie.is_leader = true;
        offset += cie.size();
        leaders.push_back(&cie);
      }
    }
  }

  // Assign FDE offsets to files.
  i64 idx = 0;
  for (ObjectFile<E> *file : ctx.objs) {
    file->fde_idx = idx;
    idx += file->fdes.size();

    file->fde_offset = offset;
    offset += file->fde_size;
  }

  // .eh_frame must end with a null word.
  this->shdr.sh_size = offset + 4;
}

gdb index

gdb-index是用于加速gdb的段,对应的链接选项

–gdb-index Create .gdb_index for faster gdb startup

这边就不具体详细介绍了,有兴趣的可以自行看一下资料

https://sourceware.org/gdb/onlinedocs/gdb/Index-Files.html

代码语言:javascript
复制
// Handle --gdb-index.
if (ctx.arg.gdb_index)
  ctx.gdb_index->construct(ctx);
代码语言:javascript
复制
// This page explains the format of .gdb_index:
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html
template <typename E>
void GdbIndexSection<E>::construct(Context<E> &ctx) {
  Timer t(ctx, "GdbIndexSection::construct");

  std::atomic_bool has_debug_info = false;

  // Read debug sections
  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    if (file->debug_info) {
      // Read compilation units from .debug_info.
      file->compunits = read_compunits(ctx, *file);

      // Count the number of address areas contained in this file.
      file->num_areas = estimate_address_areas(ctx, *file);
      has_debug_info = true;
    }
  });

  if (!has_debug_info)
    return;

  // Initialize `area_offset` and `compunits_idx`.
  for (i64 i = 0; i < ctx.objs.size() - 1; i++) {
    ctx.objs[i + 1]->area_offset =
      ctx.objs[i]->area_offset + ctx.objs[i]->num_areas * 20;
    ctx.objs[i + 1]->compunits_idx =
      ctx.objs[i]->compunits_idx + ctx.objs[i]->compunits.size();
  }

  // Read .debug_gnu_pubnames and .debug_gnu_pubtypes.
  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    file->gdb_names = read_pubnames(ctx, *file);
  });

  // Estimate the unique number of pubnames.
  HyperLogLog estimator;
  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    HyperLogLog e;
    for (GdbIndexName &name : file->gdb_names)
      e.insert(name.hash);
    estimator.merge(e);
  });

  // Uniquify pubnames by inserting all name strings into a concurrent
  // hashmap.
  map.resize(estimator.get_cardinality() * 2);
  tbb::enumerable_thread_specific<i64> num_names;

  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    for (GdbIndexName &name : file->gdb_names) {
      MapEntry *ent;
      bool inserted;
      std::tie(ent, inserted) = map.insert(name.name, name.hash, {file, name.hash});
      if (inserted)
        num_names.local()++;

      ObjectFile<E> *old_val = ent->owner;
      while (file->priority < old_val->priority &&
             !ent->owner.compare_exchange_weak(old_val, file));

      ent->num_attrs++;
      name.entry_idx = ent - map.values;
    }
  });

  // Assign offsets for names and attributes within each file.
  tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
    for (GdbIndexName &name : file->gdb_names) {
      MapEntry &ent = map.values[name.entry_idx];
      if (ent.owner == file) {
        ent.attr_offset = file->attrs_size;
        file->attrs_size += (ent.num_attrs + 1) * 4;
        ent.name_offset = file->names_size;
        file->names_size += name.name.size() + 1;
      }
    }
  });

  // Compute per-file name and attributes offsets.
  for (i64 i = 0; i < ctx.objs.size() - 1; i++)
    ctx.objs[i + 1]->attrs_offset =
      ctx.objs[i]->attrs_offset + ctx.objs[i]->attrs_size;

  ctx.objs[0]->names_offset =
    ctx.objs.back()->attrs_offset + ctx.objs.back()->attrs_size;

  for (i64 i = 0; i < ctx.objs.size() - 1; i++)
    ctx.objs[i + 1]->names_offset =
      ctx.objs[i]->names_offset + ctx.objs[i]->names_size;

  // .gdb_index contains an on-disk hash table for pubnames and
  // pubtypes. We aim 75% utilization. As per the format specification,
  // It must be a power of two.
  i64 num_symtab_entries =
    std::max<i64>(bit_ceil(num_names.combine(std::plus()) * 4 / 3), 16);

  // Now that we can compute the size of this section.
  ObjectFile<E> &last = *ctx.objs.back();
  i64 compunits_size = (last.compunits_idx + last.compunits.size()) * 16;
  i64 areas_size = last.area_offset + last.num_areas * 20;
  i64 offset = sizeof(header);

  header.cu_list_offset = offset;
  offset += compunits_size;

  header.cu_types_offset = offset;
  header.areas_offset = offset;
  offset += areas_size;

  header.symtab_offset = offset;
  offset += num_symtab_entries * 8;

  header.const_pool_offset = offset;
  offset += last.names_offset + last.names_size;

  this->shdr.sh_size = offset;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-07-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Fill gnu.version section contents
    • verdef
      • verneed
      • create_output_symtab
        • chunk
          • OutputSection
          • got
          • PLT
          • PLTGOT
        • obj
          • dso
          • eh_frame_construct
          • gdb index
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档