リンク処理(4/13)

C プロジェクトのコンパイルでは、コンパイラは C ソース ファイルを単位として受け取り、各 C ファイルを対応するオブジェクト ファイルに変換します。生成される各ターゲット ファイルは、コード セグメント、データ セグメント、BSS セグメント、シンボル テーブルなどのセクションで構成されます。これらのセクションは、対象ファイルのゼロオフセットアドレスから順に配置されており、各セクション内のシンボルのゼロアドレスからのオフセットが各シンボルのアドレスになります。すべて仮アドレス付き。

その後のリンク処理では、これらのオブジェクト ファイルの各セクションが分解されて再度アセンブルされ、各セクションの開始アドレスが変更され、その結果、各セクションで定義されている関数やグローバル変数などのシンボルのアドレスが変更されます。再変更、つまり移転。

リンクは主に、セグメントの組み立て、シンボルの解決、再配置の 3 つのプロセスに分かれます。

セグメント化されたアセンブリ

ここに画像の説明を挿入

リンク プロセスの最初のステップは、個々のオブジェクト ファイルをセグメントに組み立てることです。リンカは、コンパイラによって生成された各再配置可能オブジェクト ファイルを再アセンブルします。各オブジェクト ファイルのコード セグメントを最終的な実行可能ファイルのコード セグメントとしてまとめ、各オブジェクト ファイルのデータ セグメントを実行可能ファイルのデータ セグメントとしてまとめます。他の部分も同様に組み立てていきます。

リンカは、実行可能ファイル内にグローバル シンボル テーブルを作成し、各オブジェクト ファイルのシンボル テーブル内のシンボルを収集して、それらをグローバル シンボル テーブルに均一に配置します。このステップにより、実行可能ファイル内のすべてのシンボルは独自のアドレスを持ち、グローバル シンボル テーブルに格納されますが、この時点では、グローバル シンボル テーブル内のアドレスは各オブジェクト ファイル内の元のアドレス、つまりゼロ アドレスからのオフセットのままです。 。

リンク プロセス中にさまざまなコード部分がどのように組み立てられるのでしょうか? リンクによって生成された実行ファイルは最終的にメモリにロードされて実行されますが、メモリのどこにロードすればよいのでしょうか。一般に、このアドレスはリンクの開始アドレスです。実行可能プログラムにはエントリアドレスが必要で、通常は最初に実行するコードが先頭に配置されます。プログラムのリンクアドレスや各セグメントの組み立て順序はリンクスクリプトで指定可能

リンクスクリプトは、本質的にはスクリプトファイルであり、このスクリプトファイルには、各セグメントのアセンブリ順序、開始アドレス、位置合わせなどの情報だけでなく、出力される実行ファイル形式、動作プラットフォーム、エントリなどの情報も指定されています。アドレスが指定されている場合、詳細な説明が表示されます。リンカは、リンク スクリプトで定義されたルールに従って実行可能ファイルをアセンブルし、最終的に実行可能ファイルの ELF ヘッダ内の情報をセクションの形式で保存します。

組み込みシステムでメモリ RAM の開始アドレスが 0x60000000 である場合、プログラムをリンクするときに、リンク スクリプトでリンク開始アドレスとしてメモリ内の正当なアドレスを指定できます。プログラムの実行中、ローダーはまず実行可能ファイル内の ELF ヘッダー情報を解析し、実行中のプラットフォームとプログラムのロード アドレス情報を確認してから、実行可能ファイルをメモリ内の対応するアドレスにロードします。これにより、プログラムを実行できるようになります。 。通常は、コンパイラによって提供されるデフォルトのリンカー スクリプトを使用します。

次のコマンドを使用して、デフォルトのリンカー スクリプトを表示します。

jiaming@jiaming-pc:~/Documents/CSDN_Project$ arm-linux-gnueabi-ld --verbose
GNU ld (GNU Binutils for Ubuntu) 2.34
  Supported emulations:
   armelf_linux_eabi
   armelfb_linux_eabi
using internal linker script:
==================================================
/* Script for -z combreloc */
/* Copyright (C) 2014-2020 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm",
	      "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/arm-linux-gnueabi"); SEARCH_DIR("=/lib/arm-linux-gnueabi"); SEARCH_DIR("=/usr/lib/arm-linux-gnueabi"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/arm-linux-gnueabi/lib");
SECTIONS
{
    
    
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x00010000)); . = SEGMENT_START("text-segment", 0x00010000) + SIZEOF_HEADERS;
  .interp         : {
    
     *(.interp) }
  .note.gnu.build-id  : {
    
     *(.note.gnu.build-id) }
  .hash           : {
    
     *(.hash) }
  .gnu.hash       : {
    
     *(.gnu.hash) }
  .dynsym         : {
    
     *(.dynsym) }
  .dynstr         : {
    
     *(.dynstr) }
  .gnu.version    : {
    
     *(.gnu.version) }
  .gnu.version_d  : {
    
     *(.gnu.version_d) }
  .gnu.version_r  : {
    
     *(.gnu.version_r) }
  .rel.dyn        :
    {
    
    
      *(.rel.init)
      *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
      *(.rel.fini)
      *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
      *(.rel.data.rel.ro .rel.data.rel.ro.* .rel.gnu.linkonce.d.rel.ro.*)
      *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
      *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
      *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
      *(.rel.ctors)
      *(.rel.dtors)
      *(.rel.got)
      *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
      PROVIDE_HIDDEN (__rel_iplt_start = .);
      *(.rel.iplt)
      PROVIDE_HIDDEN (__rel_iplt_end = .);
    }
  .rela.dyn       :
    {
    
    
      *(.rela.init)
      *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
      *(.rela.fini)
      *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
      *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
      *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
      *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
      *(.rela.ctors)
      *(.rela.dtors)
      *(.rela.got)
      *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
      PROVIDE_HIDDEN (__rela_iplt_start = .);
      *(.rela.iplt)
      PROVIDE_HIDDEN (__rela_iplt_end = .);
    }
  .rel.plt        :
    {
    
    
      *(.rel.plt)
    }
  .rela.plt       :
    {
    
    
      *(.rela.plt)
    }
  .init           :
  {
    
    
    KEEP (*(SORT_NONE(.init)))
  }
  .plt            : {
    
     *(.plt) }
  .iplt           : {
    
     *(.iplt) }
  .text           :
  {
    
    
    *(.text.unlikely .text.*_unlikely .text.unlikely.*)
    *(.text.exit .text.exit.*)
    *(.text.startup .text.startup.*)
    *(.text.hot .text.hot.*)
    *(SORT(.text.sorted.*))
    *(.text .stub .text.* .gnu.linkonce.t.*)
    /* .gnu.warning sections are handled specially by elf.em.  */
    *(.gnu.warning)
    *(.glue_7t) *(.glue_7) *(.vfp11_veneer) *(.v4_bx)
  }
  .fini           :
  {
    
    
    KEEP (*(SORT_NONE(.fini)))
  }
  PROVIDE (__etext = .);
  PROVIDE (_etext = .);
  PROVIDE (etext = .);
  .rodata         : {
    
     *(.rodata .rodata.* .gnu.linkonce.r.*) }
  .rodata1        : {
    
     *(.rodata1) }
  .ARM.extab   : {
    
     *(.ARM.extab* .gnu.linkonce.armextab.*) }
  .ARM.exidx   :
    {
    
    
      PROVIDE_HIDDEN (__exidx_start = .);
      *(.ARM.exidx* .gnu.linkonce.armexidx.*)
      PROVIDE_HIDDEN (__exidx_end = .);
    }
  .eh_frame_hdr   : {
    
     *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
  .eh_frame       : ONLY_IF_RO {
    
     KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gcc_except_table   : ONLY_IF_RO {
    
     *(.gcc_except_table .gcc_except_table.*) }
  .gnu_extab   : ONLY_IF_RO {
    
     *(.gnu_extab*) }
  /* These sections are generated by the Sun/Oracle C++ compiler.  */
  .exception_ranges   : ONLY_IF_RO {
    
     *(.exception_ranges*) }
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  */
  . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
  /* Exception handling  */
  .eh_frame       : ONLY_IF_RW {
    
     KEEP (*(.eh_frame)) *(.eh_frame.*) }
  .gnu_extab      : ONLY_IF_RW {
    
     *(.gnu_extab) }
  .gcc_except_table   : ONLY_IF_RW {
    
     *(.gcc_except_table .gcc_except_table.*) }
  .exception_ranges   : ONLY_IF_RW {
    
     *(.exception_ranges*) }
  /* Thread Local Storage sections  */
  .tdata	  :
   {
    
    
     PROVIDE_HIDDEN (__tdata_start = .);
     *(.tdata .tdata.* .gnu.linkonce.td.*)
   }
  .tbss		  : {
    
     *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
  .preinit_array    :
  {
    
    
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  }
  .init_array    :
  {
    
    
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
    KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
    PROVIDE_HIDDEN (__init_array_end = .);
  }
  .fini_array    :
  {
    
    
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
    KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
    PROVIDE_HIDDEN (__fini_array_end = .);
  }
  .ctors          :
  {
    
    
    /* gcc uses crtbegin.o to find the start of
       the constructors, so we make sure it is
       first.  Because this is a wildcard, it
       doesn't matter if the user does not
       actually link against crtbegin.o; the
       linker won't look for a file to match a
       wildcard.  The wildcard also means that it
       doesn't matter which directory crtbegin.o
       is in.  */
    KEEP (*crtbegin.o(.ctors))
    KEEP (*crtbegin?.o(.ctors))
    /* We don't want to include the .ctor section from
       the crtend.o file until after the sorted ctors.
       The .ctor section from the crtend file contains the
       end of ctors marker and it must be last */
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))
  }
  .dtors          :
  {
    
    
    KEEP (*crtbegin.o(.dtors))
    KEEP (*crtbegin?.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
  }
  .jcr            : {
    
     KEEP (*(.jcr)) }
  .data.rel.ro : {
    
     *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
  .dynamic        : {
    
     *(.dynamic) }
  . = DATA_SEGMENT_RELRO_END (0, .);
  .got            : {
    
     *(.got.plt) *(.igot.plt) *(.got) *(.igot) }
  .data           :
  {
    
    
    PROVIDE (__data_start = .);
    *(.data .data.* .gnu.linkonce.d.*)
    SORT(CONSTRUCTORS)
  }
  .data1          : {
    
     *(.data1) }
  _edata = .; PROVIDE (edata = .);
  . = .;
  __bss_start = .;
  __bss_start__ = .;
  .bss            :
  {
    
    
   *(.dynbss)
   *(.bss .bss.* .gnu.linkonce.b.*)
   *(COMMON)
   /* Align here to ensure that the .bss section occupies space up to
      _end.  Align after .bss to ensure correct alignment even if the
      .bss section disappears because there are no input sections.
      FIXME: Why do we need it? When there is no .bss section, we do not
      pad the .data section.  */
   . = ALIGN(. != 0 ? 32 / 8 : 1);
  }
  _bss_end__ = .; __bss_end__ = .;
  . = ALIGN(32 / 8);
  . = SEGMENT_START("ldata-segment", .);
  . = ALIGN(32 / 8);
  __end__ = .;
  _end = .; PROVIDE (end = .);
  . = DATA_SEGMENT_END (.);
  /* Stabs debugging sections.  */
  .stab          0 : {
    
     *(.stab) }
  .stabstr       0 : {
    
     *(.stabstr) }
  .stab.excl     0 : {
    
     *(.stab.excl) }
  .stab.exclstr  0 : {
    
     *(.stab.exclstr) }
  .stab.index    0 : {
    
     *(.stab.index) }
  .stab.indexstr 0 : {
    
     *(.stab.indexstr) }
  .comment       0 : {
    
     *(.comment) }
  .gnu.build.attributes : {
    
     *(.gnu.build.attributes .gnu.build.attributes.*) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : {
    
     *(.debug) }
  .line           0 : {
    
     *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : {
    
     *(.debug_srcinfo) }
  .debug_sfnames  0 : {
    
     *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : {
    
     *(.debug_aranges) }
  .debug_pubnames 0 : {
    
     *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : {
    
     *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : {
    
     *(.debug_abbrev) }
  .debug_line     0 : {
    
     *(.debug_line .debug_line.* .debug_line_end) }
  .debug_frame    0 : {
    
     *(.debug_frame) }
  .debug_str      0 : {
    
     *(.debug_str) }
  .debug_loc      0 : {
    
     *(.debug_loc) }
  .debug_macinfo  0 : {
    
     *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : {
    
     *(.debug_weaknames) }
  .debug_funcnames 0 : {
    
     *(.debug_funcnames) }
  .debug_typenames 0 : {
    
     *(.debug_typenames) }
  .debug_varnames  0 : {
    
     *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : {
    
     *(.debug_pubtypes) }
  .debug_ranges   0 : {
    
     *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : {
    
     *(.debug_macro) }
  .debug_addr     0 : {
    
     *(.debug_addr) }
  .gnu.attributes 0 : {
    
     KEEP (*(.gnu.attributes)) }
  .note.gnu.arm.ident 0 : {
    
     KEEP (*(.note.gnu.arm.ident)) }
  /DISCARD/ : {
    
     *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}


==================================================

組み込みベアメタル環境でプログラムをコンパイルするとき、特に ARM の基礎となるコードをコンパイルするとき、さまざまなハードウェア構成、メモリ サイズ、開発ボードのアドレスに応じてリンク アドレスを柔軟に指定したり、指定されたリンク スクリプトを表示したりする必要があることがよくあります。時には自分たちでリンクを書くこともあります。

u-boot ソース コードからコンパイルされたリンク スクリプト u-boot.lds は、通常、u-boot ソース コードの最上位ディレクトリに配置されます。Linux カーネルによってコンパイルされたリンク スクリプト vmlinux.lds は、通常、arch/arm/boot/compressed/このディレクトリの下に配置されます。IDE では、デバッグ設定インターフェイスを通じてコード セグメントとデータ セグメントの開始アドレスを直接設定でき、リンカのレイアウト オプションを通じてプログラムのエントリ アドレスを設定できます。

シンボルの解決

企業のプロジェクトは通常、複数人で構成されるソフトウェア チームによって共同開発されます。プロジェクトでは、プロダクトマネージャーが機能要件を定義し、アーキテクトがシステム分析やモジュール分割を行い、各モジュールの具体的な実装を担当者が担当するのが一般的です。開発者は、それぞれのモジュールのプログラミングで問題を抱えている可能性があります。別のモジュールまたは別のファイルにあるグローバル変数と関数で、重複した名前の競合が発生する可能性があります。

これらのグローバル変数が複数のファイルで定義されている場合、リンカはリンク プロセス中に各ファイルで同じグローバル変数名または関数名が定義されていることを検出し、実行可能ファイル内のどれを使用する必要があるかというシンボルの競合が発生します。終わり?一つ? リンカには、シンボルの競合を解決するための特別なシンボル解決ルールがあります。

強いシンボルと弱いシンボル: 関数名と初期化されたグローバル変数は強いシンボルですが、初期化されていないグローバル変数は弱いシンボルです。強いシンボルを複数回定義することはできません。強いシンボルと弱いシンボルはプロジェクト内に共存できます。強いシンボルと弱いシンボルが共存すると、強いシンボルが弱いシンボルを上書きし、リンカは強いシンボルを最終シンボルとして選択します実行可能ファイル内。

リンカーを使用すると、プロジェクト内で複数の弱いシンボルを共存させることもできます。プログラムのコンパイル中に、コンパイラーが各ファイル内の初期化されていないグローバル変数を分析するとき、そのシンボルがリンク段階で採用されるか破棄されるかはわかりません。そのため、プログラムのコンパイル中に、初期化されていないグローバル変数は BSS セグメントに直接配置されません。これらの弱いシンボルは、COMMON と呼ばれる一時ブロックに配置され、シンボル テーブル内で未定義の COMMON でマークされ、オブジェクト ファイル内でそれらに記憶領域は割り当てられません。

リンク時に、リンカは複数のファイル内の弱いシンボルを比較し、最も大きなスペースを占有するものを実行可能ファイルの最終シンボルとして選択します。このとき、弱いシンボルのサイズは決定されており、直接配置されます。実行可能ファイル内、BSS セクション内

通常の状況では、初期化されたグローバル変数と関数名はデフォルトで強いシンボルとなり、初期化されていないグローバル変数はデフォルトで弱いシンボルになります。プロジェクトに特別な要件がある場合、一部の強いシンボルが弱いシンボルとして表示されることもあります。

__attribute__((weak)) int n = 100;

強シンボルと弱シンボルに対応して、強参照と弱参照という概念もあります。プログラムでは複数の関数や変数を定義できますが、変数名や関数名はシンボルであり、そのシンボルの本質、つまりシンボルの値がアドレスになります。別のファイルでは、関数名で関数を呼び出し、変数名で変数にアクセスできます。シンボルを介して関数を呼び出したり、変数にアクセスしたりすることは、通常、参照と呼ばれます。強いシンボルは強参照に対応し、弱いシンボルは弱い参照に対応します。

プログラムのリンク処理において、シンボルへの参照が強参照であり、リンク中にその定義が見つからない場合、リンクはエラーを報告せず、最終的な実行可能ファイルの生成には影響しません。実行可能ファイルが実行時にシンボルの定義を見つけられない場合、エラーが報告されます。

リンカの弱参照の処理ルールを使用すると、シンボルを参照する前に、シンボルに定義があるかどうかを判断できます。この利点は、未定義のシンボルを参照した場合、リンク段階ではエラーが報告されず、実行段階で判断して実行することで実行エラーも回避できることです。

モジュール実装のプロセスでは、ユーザーに提供される一連の API 関数を弱いシンボルとして宣言できます。

  • ライブラリ内の一部の API 関数の実装にあまり満足していない場合、またはこれらの API にバグがあり、より良い実装がある場合は、ライブラリ関数と同じ名前で関数をカスタマイズでき、それらを直接呼び出すと、衝突ではありません。
  • ライブラリの実装時に、一部の拡張機能モジュールの未完成の API が弱参照として定義される可能性があります。アプリケーション プログラムは、これらの API を呼び出す前に、まず関数が実装されているかどうかを判断し、それから呼び出して実行する必要があります。この利点は、将来新しいバージョンのライブラリがリリースされたときに、これらのインターフェイスが実装されているか削除されているかに関係なく、アプリケーションの通常のリンクと動作に影響を与えないことです。

リセット

シンボル解決後、リンク プロセスでの複数ファイルのシンボルの競合の問題が解決されました。処理後、実行可能ファイルのシンボル テーブル内の各シンボルは決定されましたが、シンボル テーブル内の各シンボルという問題がまだ残っています。シンボル値は、各オブジェクト ファイル内の各関数、グローバル変数、または元のポインタのアドレスであり、ゼロ アドレスを基にしたオフセットでもあり、リンカが各オブジェクト ファイルを再アセンブルした後、各セグメントの開始アドレスは次のようになります。変化がありました。各セグメントのシンボルアドレスもそれに応じて変化します。次に、実行可能ファイル内のグローバル シンボル テーブル内のシンボルのポインタを変更し、シンボル テーブル内のそれらの実アドレスを更新します。変更が完了した後、関数を呼び出したり、シンボリック参照を通じて変数にアクセスしたりする場合、メモリ内でそれらの実アドレスを見つけることができます。

ファイルの再配置テーブルを通じて、リンカはどのシンボルを再配置する必要があるかを知ることができます。再配置の中心的な作業は、命令内のシンボル アドレスを修正することです。これはリンク プロセスの最後のステップであり、最も重要な核心ステップでもあります。最初の 2 つのステップの操作は、実際にはこのステップのためのものです。

コンパイル段階では、コンパイラは通常、各 C ソース ファイルからターゲット ファイルを生成するプロセス中に未定義のシンボルが発生してもエラーを報告せず、これらのシンボルが別の場所で定義されている可能性があると判断します。リンクフェーズでは、リンカは他の場所でシンボルの定義を見つけられず、リンクエラーを報告します。コンパイラは、リンク段階でこれらの未定義シンボルを収集し、再配置テーブルを生成して、これらのシンボルがファイル内で参照されているが、このファイル内に定義が見つからないことをリンカに伝えます。リンク プロセス中に確認してください。

再配置テーブルには、命令コード内で再配置する必要があるシンボルのオフセット アドレスという重要な情報が含まれていますが、リンカが命令コード内の各シンボルの参照を修正する場合、膨大なシンボルの中からしか見つけることができません。このアドレス情報をもとにバイナリコードを生成します。リンカは、各オブジェクト ファイル内の再配置テーブルを読み取り、実行可能ファイル内のこれらのシンボルの新しいアドレスに従ってシンボルの再配置を実行し、命令コード内のこれらのシンボルを参照するアドレスを変更して、新しいシンボル テーブルを生成します。

コンパイルプロセス全体が終了し、実行可能オブジェクトファイルが得られます。

おすすめ

転載: blog.csdn.net/weixin_39541632/article/details/132031475