Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >mold源码阅读十二 创建一些输出段

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

作者头像
AkemiHomura
发布于 2023-10-22 08:31:29
发布于 2023-10-22 08:31:29
23900
代码可运行
举报
文章被收录于专栏:homura的博客homura的博客
运行总次数:0
代码可运行

Fill gnu.version section contents

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
| verdef | verdaux | verdef | verdaux |
						/                  /	
|     dynstr0    |    dynstr1    | ... | dynstrn |
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
|verneed|vednaux|vednaux|...|verneed|vednaux|vednaux|vednaux|

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
// Compute .symtab and .strtab sizes for each file.
create_output_symtab(ctx);
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
template <typename E>
static constexpr bool needs_thunk = requires { E::thunk_size; };

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

got

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
// .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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
// Handle --gdb-index.
if (ctx.arg.gdb_index)
  ctx.gdb_index->construct(ctx);
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
关于 Elasticsearch 段合并,这一篇说透了!
0、事出有因 您好,目前我需要将只读索引segment合并,有几个问题想要求教 1、 segment是不是合并到一个最好,及max_num_segments=1 2、合并的时候,通过 POST
铭毅天下
2020/10/26
7.4K0
关于 Elasticsearch 段合并,这一篇说透了!
【干货】Elasticsearch索引性能优化 (2)
本文翻译自QBox官方博客的“Elasticsearch索引性能优化”系列文章中的第二篇,版权归原作者所有。该系列文章共有三篇,其中第一篇已有同行翻译,参考链接 http://www.zcfy.cc/article/how-to-maximize-elasticsearch-indexing-performance-part-1-3624.html;后续还会有第三篇的推送,敬请关注。
杨振涛
2019/03/19
1.1K0
【干货】Elasticsearch索引性能优化 (2)
源码剖析:Elasticsearch 段合并调度及优化手段
经常看到集群的merge限流耗时比较高,所以想分析其原因、造成的影响、以及反思merge的一些优化手段。
铭毅天下
2023/09/09
1K0
源码剖析:Elasticsearch 段合并调度及优化手段
【ES三周年】Easticsearch OOM(内存溢出)的优化过程
首先,说明笔者的机器环境(不结合环境谈解决方案都是耍流氓): cpu 32核,内存128G,非固态硬盘: RAID0 (4T * 6),单节点,数据量在700G到1800G,索引15亿~21亿。敖丙大人,在蘑菇街,可多集群分片,固态硬盘,比不起啊。
NaughtyCat
2020/10/09
4.6K0
【ES三周年】Easticsearch OOM(内存溢出)的优化过程
Elasticsearch 段优化工具使用指南
本文描述问题及解决方法同样适用于 腾讯云 Elasticsearch Service(ES)。
岳涛
2023/08/23
6070
Elasticsearch 段优化工具使用指南
Elasticsearch merge 你懂了吗?
由于自动refresh过程每秒钟都会创建一个新的segment,不需要很长时间,segment的数量就会爆炸性增长。拥有太多的segment会严重影响ES的性能及查询效率。每个segment都会消耗文件句柄、内存和CPU等。更重要的是,每个搜索请求都必须依次检查每个段;segment越多,搜索速度就越慢。因此,如何制定合理的merge 策略以及如何自动的进行force merge是每个ES运维人员都必须学会的关键技能。
雨夜v1
2021/07/14
7K3
Elasticsearch merge 你懂了吗?
ES数据写入调优
大多数操作系统都尽可能多地为文件系统缓存使用内存,并切换出未使用的应用程序内存。这可能导致部分JVM堆被交换到磁盘上。
ruochen
2021/11/24
9020
Elasticsearch Index模块
index.number_of_shards :一个索引应该有的主分片(primary shards)数。默认是5。而且,只能在索引创建的时候设置。(注意,每个索引的主分片数不能超过1024。当然,这个设置也是可以改的,通过在集群的每个节点机器上设置系统属性来更改,例如:export ES_JAVA_OPTS="-Des.index.max_number_of_shards=128")
java架构师
2019/03/04
1K0
Elasticsearch Index模块
万文Elasticsearch巧妙的架构详解
本书作为 Elastic Stack 指南,关注于 Elasticsearch 在日志和数据分析场景的应用,并不打算对底层的 Lucene 原理或者 Java 编程做详细的介绍,但是 Elasticsearch 层面上的一些架构设计,对我们做性能调优,故障处理,具有非常重要的影响。
大数据老哥
2022/02/17
8270
万文Elasticsearch巧妙的架构详解
Elasticsearch性能优化实战指南
在当今世界,各行各业每天都有海量数据产生,为了从这些海量数据中获取想要的分析结果,需要对数据进行提取、转换,存储,维护,管理和分析。 这已然远远超出了普通处理工具、数据库等的实现能力,只有基于的分布式架构和并行处理机制的大数据工具所才能实现这些功能。Elasticsearch是响应如前所述大多数用例的最热门的开源数据存储引擎之一。
程序员追风
2019/08/02
9490
Elasticsearch性能优化实战指南
同事:膨胀了?Elasticsearch快就不需要调优了吗?
众所周知,ES 中 filter 是不参与相关性评分的,所以查询子句可以被系统进行缓存,性能要高于普通的 query 查询。bool 查询中支持 4 种子句,分别是 filter、must、must_not、should,其中 filter 和 must_not 属于过滤器,过滤器查询先于其它查询执行。另外在 function_score、constant_score 中也可以使用 filter 子句进行查询缓存。
开发者技术前线
2020/11/24
5330
Elasticsearch性能优化实战指南
在当今世界,各行各业每天都有海量数据产生,为了从这些海量数据中获取想要的分析结果,需要对数据进行提取、转换,存储,维护,管理和分析。 这已然远远超出了普通处理工具、数据库等的实现能力,只有基于的分布式架构和并行处理机制的大数据工具所才能实现这些功能。 Elasticsearch是响应如前所述大多数用例的最热门的开源数据存储引擎之一。
铭毅天下
2019/07/31
1.8K0
ElasticSearch - 海量数据索引拆分的一些思考
一开始从索引参数调整, forcemerge 任务引入等多个手段来缓解问题,但是伴随数据的快速膨胀还是遇到类似高命中查询等难以优化的问题,从而引出了索引拆分方案的探索与实施。
小小工匠
2023/08/27
7400
ElasticSearch - 海量数据索引拆分的一些思考
Elasticsearch日常运维
设置分配的最⼤失败重试次数,默认是5次,当然系统分配到达重试次数后,可以⼿动分配分⽚。"index.allocation.max_retries" : "5",
周银辉
2024/08/29
2550
Elasticsearch:Elasticsearch 中的 refresh 和 flush 操作指南
在今天的文章里,我们来主要介绍一下 Elasticsearch 的 refresh 及 flush 两种操作的区别。如果我们从字面的意思上讲,好像都是刷新的意思。但是在 Elasticsearch 中,这两种操作是有非常大的区别的。本指南将有效解决两者之间的差异。 我们还将介绍 Lucene 功能的基础知识,例如重新打开(reopen) 和提交 (commit),这有助于理解 refresh 和 flush 操作。
腾讯云大数据
2020/09/15
4.5K0
Elasticsearch:Elasticsearch 中的 refresh 和 flush 操作指南
elasticsearch数据更新与删除机制
当客户端发起更新操作时,elasticsearch首先会根据更新条件(例如:update api传入的_id,或update_by_query传入的match语句)找到相应的文档。elasticsearch使用文档的唯一标识符(_id)来定位文档。当找到要更新的文档后,elasticsearch首先会将原有的旧文档标记为删除状态。然后再将会将新文档插入到索引中。新文档具有相同的唯一标识符(_id),以此来实现文档的更新操作。
空洞的盒子
2023/11/04
4.2K3
Elasticsearch 写入优化,从 3000 到 8000/s,让你的 ES 飞起来!
点击关注公众号,Java干货及时送达 背景 基于elasticsearch-5.6.0 机器配置:3个云ecs节点,16G,4核,机械硬盘 优化前,写入速度平均3000条/s,一遇到压测,写入速度骤降,甚至es直接频率gc、oom等;优化后,写入速度平均8000条/s,遇到压测,能在压测结束后30分钟内消化完数据,各项指标回归正常。 生产配置 这里我先把自己优化的结果贴出来,后面有参数的详解: elasticsearch.yml中增加如下设置 indices.memory.index_buffer_si
Java技术栈
2022/05/12
1.6K0
Elasticsearch 写入优化,从 3000 到 8000/s,让你的 ES 飞起来!
触类旁通Elasticsearch:优化
ES提供的批量(bulk)API,可以用来一次索引多篇文档,从而大幅加快索引速度。如图1所示,可以使用http完成这个操作,并且将获得包含全部索引请求结果的答复。
用户1148526
2019/05/25
1.2K0
Elasticsearch 写入优化记录,从3000到8000/s
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/03/28
5580
干货满满丨万字超全 ElasticSearch 监控指南
导语:本文详细介绍了 ElasticSearch 如搜索性能指标、索引性能指标、内存使用和垃圾回收指标等六类监控关键指标、集群和索引两类大盘配置示例,以及 ES 在查询性能差、索引性能差的两种典型问题场景下详细的原因、排查方式和解决方案,同时也介绍了如何通过 Prometheus 监控搭建可靠的监控系统,详尽全面,推荐给大家,也欢迎各位一起交流。
腾讯云可观测平台
2024/03/15
2K0
干货满满丨万字超全 ElasticSearch 监控指南
相关推荐
关于 Elasticsearch 段合并,这一篇说透了!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验