ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

GCCでユーザー独自のセクションを定義したときの挙動

GNUコンパイラに生成コードをユーザー独自のセクションに配置するように命令したときに、デフォルトのリンカスクリプトが使用可能か実験しました.

実行環境

項目 説明
OS Windows11
MinGW(ビルド用) x86_64-posix-seh-rev0 8.1.0
MSVCビルドツール(ダンプ用) Visual Studio 2017相当

独自セクションにコードを配置する

元来コンパイラは関数や変数の性質に応じてコードを自動的に適切なセクションに配置しオブジェクトファイルを生成します. 最終的にはリンカが各オブジェクトファイルを結合し、全体としてセクションごとにコードを配置します. セクションの種類や名前は処理系に依存しますが、逆に言えばユーザーが独自に定義することもできます.

コードの一部を明示的に指定したセクションに配置するにはその旨を記述します. 今回は以下のように__attribute__を使用して明示的にセクションを指定してコードを配置しました.

// main.c

#include<stdio.h>

// 関数foo()は独自セクション.mysecに配置
void foo(void) __attribute__((section(".mysec")));

// 何も記述しない場合関数は各制御系で決められたセクションに配置される(.textセクションやPセクションと呼ばれることが多い)
int main(){
    foo();
    return 0;
}

void foo(void){
    printf("good bye\n");
}

異常のソースファイルをgccコマンドでビルドします. 何のエラーもなくビルドすることが出来ました. パスにスペースが含まれる場合ダブルクォーテーションで囲むのをお忘れなく.

somewhere> "C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\gcc.exe" main.c 

exeをダンプ(デフォルトリンカスクリプトでビルド)

MinGWについてくるreadelfを使おうとしたのですが、exeには使用できませんでした. なのでダンプツールとしてはVisual Studioのビルドツールの一部であるdumpbin.exeを使用しましょう. dumpbin.exeの詳しい使い方は以下の記事を見てください. dllもexeも同じ方法でダンプすることが出来ます.

prupru-prune.hatenablog.com

今回はセクションさえ見れれば十分なので/summaryオプションをつけてダンプします.

somewhere> dumpbin /summary a.exe
Microsoft (R) COFF/PE Dumper Version 14.16.27048.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file a.exe

File Type: EXECUTABLE IMAGE

  Summary

        1000 .CRT
        1000 .bss
        1000 .data
        1000 .debug_abbrev
        1000 .debug_aranges
        1000 .debug_frame
        2000 .debug_info
        1000 .debug_line
        1000 .debug_str
        1000 .idata
        1000 .mysec                  # .mysecセクション
        1000 .pdata
        1000 .rdata
        2000 .text
        1000 .tls
        1000 .xdata

ということで、デフォルトのリンカスクリプトを使用したにも関わらず、普通にユーザー独自のセクションにコードがロケートされました. めでたしめでたし.

デフォルトのリンカスクリプトを手に入れる

このままでは終われないので、ちゃんと自分でもリンカスクリプトを用意しましょう. gccコマンドに -Wl,option という形式でオプションを渡すとリンカ(ld.exe)へのオプションを渡すことが出来ます. これを利用してリンカに--verboseオプションを渡すとデフォルトで使用しているリンカスクリプトを表示させることが出来ます. 単に--verboseオプションをつけるとこれはリンカではなくコンパイラへのオプションになってしまうので注意です .

somewhere> "C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\gcc.exe" main.c -Wl,--verbose
GNU ld (GNU Binutils) 2.30
  Supported emulations:
   i386pep
   i386pe
using internal linker script:
==================================================
/* Default linker script, for normal executables */
/* Copyright (C) 2014-2018 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(pei-x86-64)
SEARCH_DIR("=/c/mingw810/prerequisites/x86_64-binutils-nomulti/x86_64-w64-mingw32/lib"); SEARCH_DIR("=/c/mingw810/prerequisites/x86_64-binutils-nomulti/lib"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
  /* Make the virtual address and file offset synced if the alignment is
     lower than the target page size. */
  . = SIZEOF_HEADERS;
  . = ALIGN(__section_alignment__);
  .text  __image_base__ + ( __section_alignment__ < 0x1000 ? . : __section_alignment__ ) :
  {
     KEEP(*(.init))
    *(.text)
    *(SORT(.text$*))
     *(.text.*)
     *(.gnu.linkonce.t.*)
    *(.glue_7t)
    *(.glue_7)
    . = ALIGN(8);
       /* Note: we always define __CTOR_LIST__ and ___CTOR_LIST__ here,
          we do not PROVIDE them.  This is because the ctors.o startup
          code in libgcc defines them as common symbols, with the
          expectation that they will be overridden by the definitions
          here.  If we PROVIDE the symbols then they will not be
          overridden and global constructors will not be run.

          This does mean that it is not possible for a user to define
          their own __CTOR_LIST__ and __DTOR_LIST__ symbols.  If that
          ability is needed a custom linker script will have to be
          used.  (The custom script can just be a copy of this script
          with the PROVIDE() qualifiers added).
          See PR 22762 for more details.  */
       ___CTOR_LIST__ = .;
       __CTOR_LIST__ = .;
       LONG (-1); LONG (-1);
       KEEP (*(.ctors));
       KEEP (*(.ctor));
       KEEP (*(SORT_BY_NAME(.ctors.*)));
       LONG (0); LONG (0);
       /* See comment about __CTOR_LIST__ above.  The same reasoning
          applies here too.  */
       ___DTOR_LIST__ = .;
       __DTOR_LIST__ = .;
       LONG (-1); LONG (-1);
       KEEP (*(.dtors));
       KEEP (*(.dtor));
       KEEP (*(SORT_BY_NAME(.dtors.*)));
       LONG (0); LONG (0);
     KEEP (*(.fini))
    /* ??? Why is .gcc_exc here?  */
     *(.gcc_exc)
    PROVIDE (etext = .);
     KEEP (*(.gcc_except_table))
  }
  /* The Cygwin32 library uses a section to avoid copying certain data
     on fork.  This used to be named ".data".  The linker used
     to include this between __data_start__ and __data_end__, but that
     breaks building the cygwin32 dll.  Instead, we name the section
     ".data_cygwin_nocopy" and explicitly include it after __data_end__. */
  .data BLOCK(__section_alignment__) :
  {
    __data_start__ = . ;
    *(.data)
    *(.data2)
    *(SORT(.data$*))
    KEEP(*(.jcr))
    __data_end__ = . ;
    *(.data_cygwin_nocopy)
  }
  .rdata BLOCK(__section_alignment__) :
  {
    *(.rdata)
             *(SORT(.rdata$*))
    __rt_psrelocs_start = .;
    KEEP(*(.rdata_runtime_pseudo_reloc))
    __rt_psrelocs_end = .;
  }
  __rt_psrelocs_size = __rt_psrelocs_end - __rt_psrelocs_start;
  ___RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
  __RUNTIME_PSEUDO_RELOC_LIST_END__ = .;
  ___RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size;
  __RUNTIME_PSEUDO_RELOC_LIST__ = . - __rt_psrelocs_size;
  .eh_frame BLOCK(__section_alignment__) :
  {
    KEEP (*(.eh_frame*))
  }
  .pdata BLOCK(__section_alignment__) :
  {
    KEEP(*(.pdata*))
  }
  .xdata BLOCK(__section_alignment__) :
  {
    KEEP(*(.xdata*))
  }
  .bss BLOCK(__section_alignment__) :
  {
    __bss_start__ = . ;
    *(.bss)
    *(COMMON)
    __bss_end__ = . ;
  }
  .edata BLOCK(__section_alignment__) :
  {
    *(.edata)
  }
  /DISCARD/ :
  {
    *(.debug$S)
    *(.debug$T)
    *(.debug$F)
    *(.drectve)
     *(.note.GNU-stack)
     *(.gnu.lto_*)
  }
  .idata BLOCK(__section_alignment__) :
  {
    /* This cannot currently be handled with grouped sections.
        See pep.em:sort_sections.  */
    KEEP (SORT(*)(.idata$2))
    KEEP (SORT(*)(.idata$3))
    /* These zeroes mark the end of the import list.  */
    LONG (0); LONG (0); LONG (0); LONG (0); LONG (0);
    KEEP (SORT(*)(.idata$4))
    __IAT_start__ = .;
    SORT(*)(.idata$5)
    __IAT_end__ = .;
    KEEP (SORT(*)(.idata$6))
    KEEP (SORT(*)(.idata$7))
  }
  .CRT BLOCK(__section_alignment__) :
  {
    ___crt_xc_start__ = . ;
    KEEP (*(SORT(.CRT$XC*)))  /* C initialization */
    ___crt_xc_end__ = . ;
    ___crt_xi_start__ = . ;
    KEEP (*(SORT(.CRT$XI*)))  /* C++ initialization */
    ___crt_xi_end__ = . ;
    ___crt_xl_start__ = . ;
    KEEP (*(SORT(.CRT$XL*)))  /* TLS callbacks */
    /* ___crt_xl_end__ is defined in the TLS Directory support code */
    ___crt_xp_start__ = . ;
    KEEP (*(SORT(.CRT$XP*)))  /* Pre-termination */
    ___crt_xp_end__ = . ;
    ___crt_xt_start__ = . ;
    KEEP (*(SORT(.CRT$XT*)))  /* Termination */
    ___crt_xt_end__ = . ;
  }
  /* Windows TLS expects .tls$AAA to be at the start and .tls$ZZZ to be
     at the end of the .tls section.  This is important because _tls_start MUST
     be at the beginning of the section to enable SECREL32 relocations with TLS
     data.  */
  .tls BLOCK(__section_alignment__) :
  {
    ___tls_start__ = . ;
    KEEP (*(.tls$AAA))
    KEEP (*(.tls))
    KEEP (*(.tls$))
    KEEP (*(SORT(.tls$*)))
    KEEP (*(.tls$ZZZ))
    ___tls_end__ = . ;
  }
  .endjunk BLOCK(__section_alignment__) :
  {
    /* end is deprecated, don't use it */
    PROVIDE (end = .);
    PROVIDE ( _end = .);
     __end__ = .;
  }
  .rsrc BLOCK(__section_alignment__) : SUBALIGN(4)
  {
    KEEP (*(.rsrc))
    KEEP (*(.rsrc$*))
  }
  .reloc BLOCK(__section_alignment__) :
  {
    *(.reloc)
  }
  .stab BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.stab)
  }
  .stabstr BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.stabstr)
  }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section.  Unlike other targets that fake this by putting the
     section VMA at 0, the PE format will not allow it.  */
  /* DWARF 1.1 and DWARF 2.  */
  .debug_aranges BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_aranges)
  }
  .zdebug_aranges BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_aranges)
  }
  .debug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_pubnames)
  }
  .zdebug_pubnames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_pubnames)
  }
  .debug_pubtypes BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_pubtypes)
  }
  .zdebug_pubtypes BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_pubtypes)
  }
  /* DWARF 2.  */
  .debug_info BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_info .gnu.linkonce.wi.*)
  }
  .zdebug_info BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_info .zdebug.gnu.linkonce.wi.*)
  }
  .debug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_abbrev)
  }
  .zdebug_abbrev BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_abbrev)
  }
  .debug_line BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_line)
  }
  .zdebug_line BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_line)
  }
  .debug_frame BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_frame)
  }
  .zdebug_frame BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_frame)
  }
  .debug_str BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_str)
  }
  .zdebug_str BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_str)
  }
  .debug_loc BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_loc)
  }
  .zdebug_loc BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_loc)
  }
  .debug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_macinfo)
  }
  .zdebug_macinfo BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_macinfo)
  }
  /* SGI/MIPS DWARF 2 extensions.  */
  .debug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_weaknames)
  }
  .zdebug_weaknames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_weaknames)
  }
  .debug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_funcnames)
  }
  .zdebug_funcnames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_funcnames)
  }
  .debug_typenames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_typenames)
  }
  .zdebug_typenames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_typenames)
  }
  .debug_varnames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_varnames)
  }
  .zdebug_varnames BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_varnames)
  }
  .debug_macro BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_macro)
  }
  .zdebug_macro BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_macro)
  }
  /* DWARF 3.  */
  .debug_ranges BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_ranges)
  }
  .zdebug_ranges BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_ranges)
  }
  /* DWARF 4.  */
  .debug_types BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_types .gnu.linkonce.wt.*)
  }
  .zdebug_types BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_types .zdebug.gnu.linkonce.wt.*)
  }
  /* For Go and Rust.  */
  .debug_gdb_scripts BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.debug_gdb_scripts)
  }
  .zdebug_gdb_scripts BLOCK(__section_alignment__) (NOLOAD) :
  {
    *(.zdebug_gdb_scripts)
  }
}


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

この部分をコピペして新しくld.ldsというファイルにしましょう. そして次の追加のセクション情報をいい感じのところに記述します.

// ld.lds

.mysec BLOCK(__section_alignment__) :
{
  *(.mysec)
}

独自リンカスクリプトでビルド

独自リンカスクリプトの準備はできたので、後はリンカにこの独自リンカスクリプトを使用してロケートするようにオプションを指定します. -cオプション、あるいは-Tオプションを使用すれば明示的に使用するリンカオプションを指定することが出来ます. いわく、リンカスクリプトのことをリンクコマンドファイルともいうので-cオプションと言いらしいです. -Tオプションは-cオプションと全く同じですが、他のツールとの互換性のために残されているとのこと.

とにもかくにも次のコマンドでビルドします.

somewhere> "C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin\gcc.exe" main.c -Wl,--verbose -T ld.lds

すると先ほどは using internal linker script: と表示されていた部分が using external linker script: となっているはずです.

exeをダンプ(独自リンカスクリプトでビルド)

独自リンカスクリプトを用いてロケートした.exeのセクション情報をを先ほどと同様にダンプすると次のように表示されました.

somewhere>dumpbin /summary a.exe
Microsoft (R) COFF/PE Dumper Version 14.16.27048.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file a.exe

File Type: EXECUTABLE IMAGE

  Summary

        1000 .CRT
        1000 .bss
        1000 .data
        1000 .debug_abbrev
        1000 .debug_aranges
        1000 .debug_frame
        2000 .debug_info
        1000 .debug_line
        1000 .debug_str
        1000 .idata
        1000 .mysec
        1000 .pdata
        1000 .rdata
        2000 .text
        1000 .tls
        1000 .xdata

全く同じですね.

dumpbin /all でダンプしてみる

なんとか差分を見つけたいので両者をdumpbin /all でダンプしてみると、タイムスタンプ以外にセクションの順番が違うということが分かりました. が、それ以外に違いらしい違いは見られませんでした.

結論

コンパイラに生成コードをユーザー独自のセクションに配置するように命令したとしても、自動的にそのセクションが作成されるので、独自リンカスクリプトを用意する必要はない.

参考

linuxjm.osdn.jp