[Linker] GNU linker LD 사용하기 – Linker Scripts (.ld)

모든 link는 linker script로 제어된다. 이 스크립트는 linker script language로 쓰여진다.

linker script의 목적은 input file 내의 section을 output file로 어떻게 맵핑(map)해야 하는지, 그리고 output file의 memory layout을 어떻게 해야 하는지 설명하는 거다. 대부분의 linker script는 이 이상을 하지 않는다. 그러나 필요하면 linker script는 linker에게 아래에 설명된 command들을 사용해서 다른 동작을 수행할 수 있다.

linker는 언제나 linker script를 사용한다. 제공되지 않으면 linker는 linker executable 내부에 compile된 default script를 사용한다. '--verbose' command line option을 사용해서 default linker script를 볼 수 있다. '-r' 이나 '-N' 같은 command line option은 default linker script에 영향을 준다.

자신만의 linker script를 '-T' command line option을 사용해서 줄 수 있다. 이렇게 하면 그 linker script가 default linker script를 대신한다. 또한, input 파일처럼 이름을 붙여서 링커에게 줄 수도 있다.

기본적인 개념

링커는 input file들을 하나의 output file로 합친다. input file과 각 output file은 object file format으로 되어 있다. 그래서 각 파일을 object file이라고 부른다. output file은 executable이라고 부르기도 한다. 각 object file은 sections의 리스트를 가진다. input file 안에 있는 section을 input section이라고 하고, output file 안에 있는 section을 output section이라고 한다.

각 object file 안의 section은 하나의 name과 하나의 size를 갖는다. 거의 모든 섹션은 section contents라고 하는 data의 associated block을 갖는다. loadable로 표시된 section은 output file이 실행될 때 memory로 load된다. content가 없는 section은 allocatable일 수 있는데 이는 메모리는 set되지만 거기에 특별한 게 load되지는 않는다(특정 경우에 이 메모리는 0으로 초기화된다)는 뜻이다. loadable도 allocatable도 아닌 섹션은 일반적으로 디버깅 정보를 갖는다.

모든 loadable 또는 allocatable output section은 두가지 address를 갖는다. 먼저 VMA(virtual memory address)는 output file이 실행될 때 갖을 address이다. 다음으로 LMA(load memory address)는 그 section이 load될 address이다. 대부분의 경우에 두 address는 같다. 다를 수 있는 예는 data section이 ROM에 load되고 program이 시작될 때 RAM으로 복사될 때이다(이 테크닉은 ROM based system에서 global variables을 초기화할 때 자주 쓰인다). 이 경우 ROM address는 LMA가 되고, RAM address는 VMA가 될 것이다.

objdump -h 로 object file의 section들을 볼 수 있다.

모든 object file은 symbol table이라고 부르는 symbol의 list를 갖는다. symbol은 defined 되거나 undefined 될 수 있다. 각 symbol은 이름을 갖고, 각 defined symbol은 다른 정보들과 함께 address를 갖는다. C/C++을 object file로 컴파일하면 모든 defined function과 global/static variable의 defined symbol을 얻게 된다. input file에서 참조되는 모든 undefined function과 global variable은 undefined symbol이 될 거다.

nm이나 objdump -t로 object file의 symbol을 볼 수 있다.

Linker Script 형식

Linker script는 command로 이뤄진 text file이다. 각 command는 argument를 갖는 keyword이거나 또는 symbol로의 assignment 둘 중 하나다. ';'으로 각 command를 구분할 수 있다. white space는 일반적으로 무시된다.

file이나 format name같은 string은 직접 들어갈 수 있다. file name에 다른 file name을 구별할 때 쓰이는 ','같은 문자가 포함되어 있으면, ""로 감쌀 수 있다. “”를 파일 이름으로 쓸 수 있는 방법은 없다.

C처럼 linker script안에 '/*', '*/'로 comment를 달 수 있다.

간단한 linker script 예제

많은 linker script들은 정말 간단하다.

가능한 가장 간단한 linker script는 딱 하나의 'SECTIONS' command를 갖는 것이다. 'SECTIONS' command는 output file의 memory layout을 설명하는데 쓰인다.

'SECTIONS' command는 강력한 command이다. 여기서 우리는 가장 간단히 사용하는 것을 설명할거다. 여러분의 프로그램이 code, initialized data, uninitialized data로만 구성되어 있다고 하면 거기에는 '.text', '.data', '.bss' section이 있게 된다. 이에 더해서 여러분의 input files안에 있는 section들만 있다고 하자.

이 예제를 위해 그 code가 address 0x10000에 load되어야 하고, 그 data는 address 0x8000000에서 시작되어야 한다고 하자.

아래가 이를 위한 link script이다.

SECTIONS
{
  . = 0x10000;
  .text : { *(.text) }
  . = 0x8000000;
  .data : { *(.data) }
  .bss : { *(.bss) }
}

'SECTIONS' command는 'SECTIONS' 키워드, 그 뒤에 symbol assignments과 output section 설명을 ‘{‘, ‘}’로 감싸서 쓴다.

위 예제의 첫 줄은 location counter인 special symbol ‘.’을 지정한다. 다른 방법(이건 뒤에 설명)으로 output section의 주소를 지정하지 않으면, location counter의 현재 값으로부터 그 address가 지정된다. 그러고 나면 location counter는 output section의 size만큼 더해진다. 'SECTIONS' command를 시작할 때  location counter는 값 '0'을 갖는다.

두번째 줄은 output section ‘.text’를 define한다. ':'이 필요하다. '{', '}' 안에, output section name 뒤에 이 output section에 두어야 할 input section들의 이름을 나열한다. '*'는 모든 파일 이름에 매치되는 wildcard이다. 표현식 '*(.text)' 은 모든 input file들 안의 '.text' input section을 뜻한다.

location counter가 '0x10000'이었기 때문에, output section '.text'가 defined될 때, linker는 output file내의 '.text' section의 주소를 '0x10000'으로 지정할 거다.

뒤의 줄들은 output file의 '.data''.bss'를 define한다. linker는 '.data' output section을 '0x8000000'에 둔다. 그러고나면 location counter는 '0x8000000' + '.data' output section의 size가 된다. 그 결과 linker는 '.bss' output section을 메모리 내에서 '.data' output section 바로 뒤에 두게 된다.

linker는 필요하다면 location counter를 더해서 각각의 output section에 필요한 alignment를 맞춘다. 이 예제에서는 '.text''.data' section들의 지정된 주소들은 아마 alignment constraint를 만족할 것이고 linker는 '.data''.bss' section 사이에 작은 갭을 만들어야 했을 수 있다.

이게 다다. 간단하고 완전한 하나의 linker script다.

간단한 Linker Script Command

간단한 linker script command들을 설명한다.

  • Entry Point: entry point를 설정
  • File Command들: 파일을 다루는 command들
  • Format Command들: object file format을 다루는 command들
  • 기타 Command들: 다른 linker script command들

Entry point 설정

프로그램 내의 실행되는 첫번째 instruction을 entry point라고 부른다. ENTRY linker script command로 이 entry point를 설정할 수 있다. 그 argument는 symbol name이다:

ENTRY(symbol)

entry point를 설정하는 여러 방법이 있다. linker는 다음 방법들을 순서대로 실행하면서 entry point를 설정하고, 그 중 하나가 성공하면 멈춘다:

  • '-e' entry command-line 옵션;
  • linker script 안의 ENTRY(symbol) command;
  • symbol start가 defined되어 있다면 그 값;
  • '.text' section이 있다면 그 첫 byte의 address;
  • address 0.

파일을 다루는 command들

여러 linker script command들이 파일을 다룬다.

INCLUDE filename

linker script filename 을 이 지점에 include한다. 현재 디렉토리에서 찾고 나서 -L 옵션으로 지정된 디렉토리 내에서 찾는다. INCLUDE는 10 레벨까지 nest call이 가능하다.

INPUT(filefile, ...)

INPUT(file file ...)

INPUT command 는 linker에게 그 link 내에 command line 상에서 정한 것처럼 정해진 file들을 include하도록 지시한다. 예를 들어, 'subr.o'를 link하는 모든 때에 include하고 싶지만 매번 command line에 이를 입력하고 싶지는 않을 때 'INPUT (subr.o)'를 linker script 내에 적을 수 있다. 사실 linker script 안에 모든 input files를 적어두고, linker를 아무 것도 없이 '-T' 옵션으로 실행할 수 있다. linker는 먼저 현재 디렉토리 내에서 그 파일을 열 수 있는지 시도해보고, 없으면, archive library search path를 통해 찾는다. Command Line Options section 내의 '-L' 의 설명을 한번 봐라. 'INPUT (-lfile)'을 쓰면 ld 는 command line argument '-l'로 한 것 처럼 libfile.a로 그 이름을 바꾼다. implicit linker script 내에서 INPUT command를 사용하면, 그 파일들은 그 link상의 linker script file이 included된 그 지점에서 include될 것이다. 이는 archive searching에 영향을 준다.

GROUP(filefile, ...)

GROUP(file file ...)

GROUP command는 정해진 file들 모두 archive되어야 한다는 것, 그리고 새로운 undefined reference가 만들어지기 전까지 반복적으로 찾아진다는 것을 제외하면 INPUT과 비슷하다. Command Line Options section 내의 '-(' 의 설명을 봐라.

OUTPUT(filename)

OUTPUT command는 output file의 이름을 정한다. linker script 안에서 OUTPUT(filename) 을 사용하는 것은 command line (Command Line Options section을 봐라) 상에서 '-o filename' 을 사용하는 것과 정확히 같다. 둘 다 사용되면, command line option이 더 우선된다. OUTPUT command는 보통 기본 값인 'a.out' 말고 다른 file을 기본 값으로 define하려고 할 때 사용할 수 있다.

SEARCH_DIR(path)

SEARCH_DIR command는 ld가 library들을 archive할 때 보는 경로의 리스트에 path를 추가한다. SEARCH_DIR(path)를 사용하는 것은 command line (Command Line Options section을 봐라) 상에서 '-L path'를 사용하는 것과 정확히 같다. 둘 다 사용되면, linker는 두 path 모두 찾는다. command line option에서 지정된 path를 먼저 찾는다.

STARTUP(filename)

STARTUP command는 command line 상에서 먼저 정해졌던 것처럼 그 filename이 link될 첫 input file이 되는 것을 제외하면 INPUT command와 똑같다. 이는 entry point가 언제나 첫 file의 시작인 시스템을 사용할 때 유용할 거다.

Object file format을 다루는 command들

두 linker script command가 object file format을 다룬다.

OUTPUT_FORMAT(bfdname)

OUTPUT_FORMAT(defaultbiglittle)

(생략)

TARGET(bfdname)

(생략)

다른 linker script command들

다른 linker script command들이 몇 있다.

ASSERT(expmessage)

exp 가 0이 아님을 확인한다. 0이라면, linker가 error code와 message를 찍으면서 종료된다.

EXTERN(symbol symbol ...)

output file에 들어갈 symbol을 undefined symbol로 강제한다. 예를 들면 이는 standard library들로부터 추가 모듈들의 linking을 trigger할 수 있다. 각 EXTERN마다 여러 symbol들을 나열할 수 있고, EXTERN은 여러 번 사용할 수 있다. 이 command는 '-u' command-line option과 같은 효과를 갖는다.

FORCE_COMMON_ALLOCATION

이 command는 '-d' command-line option과 같은 효과를 갖는다. relocatable output file이 지정 ('-r') 되었다 하더라도, common symbol들에 ld가 space를 assign하도록 만든다.

NOCROSSREFS(section section ...)

이 command는 ld에게 어떤 output section들 사이의 모든 reference들에 관한 error를 올리라고 말하는데 사용될 수 있다. 어떤 종류의 program에서, 특히 임베디드 시스템 상에서 overlay가 사용될 때, 한 section이 메모리로 load되면, 다른 section은 아닐 것이다. 두 section 사이의 모든 direct reference는 error가 된다. 예를 들어, 한 section의 code가 다른 section에 defined된 함수를 호출한다면 에러일 거다. NOCROSSREF command는 output section 이름의 list를 가져온다. ld가 section들 사이에 어떤 cross references가 발견하면 error를 보고하고, 0이 아닌 exit status로 return한다. NOCROSSREF command는 input section name이 아닌 output section name을 사용한다.

OUTPUT_ARCH(bfdarch)

특정 output machine architecture를 지정한다. argument는 BFD library (BFD section을 봐라) 에 의해 사용되는 이름 중 하나다. objdump'-f' 옵션을 사용해서 object file의 architecture를 볼 수 있다.

Symbol에 값 assign하기

linker script 안에서 symbol에 값을 assign할 수 있다. 이는 symbol을 global symbol로 define한다.

간단한 assignment

C assignment operators의 하나를 사용해서 symbol을 assign할 수 있다.

symbol = expression ;

symbol += expression ;

symbol -= expression ;

symbol *= expression ;

symbol /= expression ;

symbol <<= expression ;

symbol >>= expression ;

symbol &= expression ;

symbol |= expression ;

첫번째 경우는 expression의 값으로 symbol을 define한다. 다른 모든 경우는, symbol이 이미 defined되어 있어야 하고, 값이 그에 따라 조정될 것이다.

special symbol name '.' 은 location counter를 나타낸다. SECTIONS command 내에서만 이를 사용할 거다.

expression 뒤에 ';'을 꼭 써야 한다.

Expression들은 아래에 defined되어 있다. Linker Script내의 expression 절을 봐라.

symbol assignment는 command들의 오른쪽에 command처럼, SECTIONS command 내의 statement처럼, SECTIONS command 내의 output section description의 일부처럼 작성될 수 있다.

symbol의 section은 그 expression의 section으로부터 정해진다. Expression의 Section 절을 봐라.

여기 symbol assignment가 사용될 수 있는 세가지 다른 곳을 보여주는 예제가 있다.

floating_point = 0;
SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
    }
  _bdata = (. + 3) & ~ 4;
  .data : { *(.data) }
}

이 예제에서 symbol 'floating_point'는 0으로 defined될 것이다. symbol '_etext'는 ‘.text’ input section 뒤의 address로 defined될 것이다. symbol '_bdata'는 ‘.text’ output section 뒤에 4 byte boundary로 올려서 aligned된 address로 defined될 것이다.

PROVIDE

어떤 경우에 어떤 symbol이 reference되긴 하지만 그 link에 포함된 어떤 object에도 defined되어 있지 않을 때 linker script가 그 symbol만 define하고 싶을 수 있다. 예를 들면, symbol 'etext'가 defined된 traditional linker들 말이다. 그러나 ANSI C는 error 없이 'etext'를 function name처럼 사용할 수 있어야 함을 요구한다. PROVIDE keyword는 reference되지만, defined되지는 않는 'etext'같은 symbol을 define하는데 사용될 수 있다. syntax는 PROVIDE(symbol = expression) 이다.

여기 'etext'를 define하는 PROVIDE 사용 예제다.

SECTIONS
{
  .text :
    {
      *(.text)
      _etext = .;
      PROVIDE(etext = .);
    }
}

이 예제에서 프로그램이 '_etext'를 (앞에 _를 붙여서) define한다면, linker는 multiple definition error가 날 거다. 다르게 말하면, 그 프로그램이 'etext'를 (앞에 _를 붙이지 않고) define한다면, linker는 프로그램에서 그 definition을 조용히 사용할 것이다. program이 'etext'를 reference하긴 하지만, define하지는 않았다면, linker는 그 linker script 안의 definition을 사용할 것이다.

SECTIONS command

SECTIONS command는 linker에게 input section을 어떻게 output sections으로 map할지, 그리고 output section을 메모리 내에 어떻게 둘지를 알린다.

SECTIONS command의 형식은 다음과 같다.

SECTIONS
{
  sections-command
  sections-command
  ...
}

sections-command는 다음 중 하나가 될 수 있다.

  • ENTRY command (entry point 설정하기 절을 보라)
  • symbol assignment (Symbol에 값 assign하기)
  • output section description
  • overlay description

ENTRY command와 symbol assignment는 편의를 위해 이들 command들 내에 location counter를 사용해서 SECTIONS command 내에 있을 수 있다. 이는 또한 output file의 layout 내에서 의미 있는 지점에서 이들 command를 사용할 수 있기 때문에 linker script를 이해하기 쉽게 만들어 준다.

Output section description과 overlay description은 아래에서 설명한다.

linker script에서 SECTIONS command를 사용하지 않으면, linker는 각 input section을 input file에서 처음 나오는 section 순서대로 이름이 같은 output section내에 둘 것이다. 예를 들어, 첫번째 파일 내에 모든 input section들이 있으면 output file 내의 section들의 순서는 첫번째 파일 내의 순서와 같을 거다. 첫 section은 address 0에 있을 것이다.

Output section description

output section의 전체 description은 다음과 같다.

section [address] [(type)] : [AT(lma)]
  {
    output-section-command
    output-section-command
    ...
  } [>region] [:phdr :phdr ...] [=fillexp]

대부분의 output section은 거의 대부분의 output section attribute를 사용하지 않는다.

section 앞 뒤로 whitespace가 있어야 하므로 section 이름은 모호하지 않게 된다. ‘:’과 ‘{‘, ‘}’ 또한 있어야 한다. 줄을 바꾸거나 다른 white space는 맘대로 하면 된다.

output-section-command는 다음 중 하나일 수 있다.

  • symbol assignment (Symbol에 값 assign하기 절을 봐라)
  • input section description (Input section description 절을 봐라)
  • 직접 포함하는 data 값 (Output section data 절을 봐라)
  • special output section keyword (Output section keyword 절을 봐라)

Output section 이름

output section의 이름은 section이다. section은 output format에 따라 제약 사항을 만족해야 한다. 적은 수의 section만을 지원하는 format에서는 a.out같은 이름이 그 format에 의해 지원되는 이름 중의 하나여야 한다(예를 들어, a.out'.text', '.data' 또는 '.bss'만 허용한다). output format이 (Oasys 처럼) 이름이 아닌 숫자로만 모든 section을 지원하면 그 이름은 따옴표로 둘러싼 숫자로된 string으로만 넣어야 한다. section 이름은 문자들로 아무렇게나 구성될 수 있지만 ‘,’같은 일반적인 문자가 있는 이름은 따옴표로 감싸야 한다.

output section 이름 '/DISCARD/'는 좀 특별하다; Output section 폐기 절 참고

Output section address

address는 output section의 VMA (virtual memory address) 에 대한 expression이다. address를 주지 않으면, linker는 region이 있다면 이를 기준으로 설정하고, 아니면 location counter의 현재 값을 기준으로 설정한다.

address를 주면 output section의 address는 그걸로 정확히 설정된다. addressregion 둘 다 주지 않으면, output section의 address는 location counter의 현재 값을 output section의 alignment 요구사항에 맞춰 align된 값으로 설정된다. output section의 alignment 요구사항은 output section 내에 포함된 모든 input section 중 가장 엄격한 alignment가 적용된다.

예를 들어,

.text . : { *(.text) }

.text : { *(.text) }

는 미묘하게 다르다. 첫번째 것은 '.text' output section의 address를 location counter의 현재 값으로 설정할 것이다. 두번째 것은 location counter의 현재 값이 '.text' input section의 가장 엄격한 alignment로 aligned된 값으로 설정된다.

address는 임의의 expression이 될 수 있다; Linker Script내의 Expression 절을 참고. 예를 들어, 0x10 boundary 상에 section을 align하길 원하면, 그 section address의 마지막 4 비트는 0이 되고, 다음처럼 하면 된다.

.text ALIGN(0x10) : { *(.text) }

이는 ALIGN이 현재 location counter를 지정된 값으로 올려서 aligned해서 return하므로 잘 동작한다.

section에 address를 지정하는 것은 location counter의 값을 바꾼다.

Input section description

가장 일반적인 output section command는 input section description이다.

input section description은 가장 기본적인 linker script operation이다. linker에게 메모리 내에 프로그램을 어떻게 lay out할지를 이야기하기 위해 output sections을 사용한다. linker에게 memory layout내로 input file들을 어떻게 map할지를 이야기하기 위해 input section description을 사용한다.

Input section 기본

input section description은 file name으로 구성된다. file name 뒤에는 괄호로 둘러싸인 section name들의 list가 option으로 붙을 수 있다.

file name과 section name은 아래에서 더 설명될 wildcard pattern(Input section wildcard pattern)이 될 수 있다.

가장 일반적인 input section description은 모든 input section을 output section내의 특정 name으로 포함시키는 것이다. 예를 들어, 모든 input '.text' section을 포함시키려면, 아래처럼 쓸 수 있다:

*(.text)

여기서 '*'은 모든 file name에 매치되는 wildcard이다.

둘 이상의 section을 포함하는 두가지 방법이 있다.

*(.text .rdata)
*(.text) *(.rdata)

이 둘의 차이점은 '.text''.rdata' input section이 output section에서 나타날 순서다. 첫번째 예제에서는 이들이 섞인다. 두번째 예제에서는 모든 '.text' input section이 먼저 나타나고 그 다음에 모든 '.rdata' input section들이 뒤따른다.

특정 파일로부터 section들을 포함시킬 때 file name을 지정할 수 있다. 하나 또는 그 이상의 file들이 메모리 내의 특정 위치에 있을 필요가 있는 special data를 갖고 있다면 예를 들어 이렇게 쓸 수 있다.

data.o(.data)

section의 list 없이 file name을 사용한다면, input file내의 이들 모든 section들이 output section 안에 포함될 것이다. 이는 보통 하진 않지만 가끔은 유용할 수 있다. 예를 들면 다음과 같다.

data.o

wild card character가 들어있지 않은 file name을 사용하면 linker는 먼저 linker command line이나 INPUT command 안에 file name이 같이 지정되어 있는지를 먼저 볼 거다. 아니라면 linker는 command line 상에 있을 때처럼 input file로 그 file을 열려고 할거다. 이는 linker가 archive search path에서 그 file을 찾지 않으므로 INPUT command와는 차이가 있다는 것에 주의해라.

Input section wildcard 패턴

input section description에서 file name 또는 section name 아니면 둘 다 wildcard pattern이 될 수 있다.

많은 예제 내에서 봤던 '*'의 file name은 file name에 대한 간단한 wildcard pattern이다.

wildcard pattern은 Unix shell에서 사용되는 것과 비슷하다.

'*'

어떤 수의 character들과도 match

'?'

어떤 single character와 match

'[chars]'

chars의 어떤 single instance와 match; '-' 문자는 소문자를 match하기 위한 '[a-z]'에서처럼 문자의 range를 정하는데 사용된다.

'\'

이 뒤의 다음 character를 quote한다.

file name이 wildcard와 match될 때 wildcard characters는 (Unix 상에서 directory를 나누는데 사용되는) '/' character와는 match되지 않는다. '*'  character를 포함하는 pattern은 예외다; 이는 '/'이 들어 있든 말든 항상 어떤 file name과도 match된다. section 이름에서는 wildcard character가 '/' character와 match된다.

file name wildcard pattern은 command line이나 INPUT command 상에서 명확히 지정된 file들만 match한다. linker는 wildcard를 확장해서 directory를 찾지 않는다.

어떤 file이 하나 또는 그 이상의 wildcard pattern과 match되거나, 또는 어떤 file이 그 file name이 명확히 나타나면서 하나의 wildcard pattern에도 맞으면, linker는 linker script 내에서 먼저 match되는 것을 사용한다.  예를 들어, 다음 순서의 input section description은 'data.o' rule이 사용되지 않기 때문에 아마 error일 거다.

.data : { *(.data) }
.data1 : { data.o(.data) }

보통 linker는 link하는 동안 보인 순서대로 wildcard에 맞는 file과 section을 둔다. 이를 괄호로 싸인 wildcard pattern 전에 (예를 들면, SORT(.text*)처럼) SORT keyword를 써서 바꿀 수 있다. SORT keyword를 쓰면 linker는 output file 안에 file과 section을 두기 전에 name으로 오름차순 정렬한다.

input section들이 가는데가 헷갈린다면 map file을 생성하는 '-M' 링커 옵션을 써라. 이 map 파일은 input section이 output section으로 정확히 어떻게 mapped되는지를 보여준다.

다음 예제는 wildcard pattern이 file을 나누는데 어떻게 사용되는지를 보여준다. 이 linker script는 linker에게 '.text' section을 '.text'안에, 모든 '.bss' section을 '.bss'안에 두도록 지시한다. linker는 대문자로 시작하는 모든 파일로부터의 '.data' section을 '.DATA' 안에 둘 거다. 다른 모든 파일은 linker가 '.data' 안에 '.data'를 둘 거다.

SECTIONS {
  .text : { *(.text) }
  .DATA : { [A-Z]*(.data) }
  .data : { *(.data) }
  .bss : { *(.bss) }
}

common symbol에 대한 input section

common symbol을 위해서는 특별한 표기법이 필요하다. 왜냐하면 많은 object file format에서 common symbol들은 특정 input section을 갖지 않기 때문이다. linker는 common symbol들을 그이름이 'COMMON'인 input section 내에 있는 것처럼 다룬다.

다른 input section들로 하는 것처럼 그냥 'COMMON' section과 file name을 사용할 수 있다. 다른 input file들로부터의 common symbol들을 또 다른 section 내에 두는 것에 비해 한 section 내에 특정 input file로부터의 common symbol들을 두는데 이를 사용할 수 있다.

대부분의 경우에 input file내의 common symbol들은 output file내의 '.bss' section내에 두게 된다. 예를 들면:

.bss { *(.bss) *(COMMON) }

어떤 object file format들은 하나 이상의 common symbol 타입을 갖는다. 예를 들면, MIPS ELF object file format은 standard common symbol들과 small common symbol을 구별한다. 이 경우, linker는 다른 common symbol 타입을 위한 다른 특별한 section name과 '.scommon'을 small common symbol을 위해 사용할 거다. 이는 common symbol의 다른 타입을 메모리의 다른 위치에 map할 수 있도록 한다.

오래된 linker scripts에서는 '[COMMON]'을 볼 수도 있다. 이 표기는 지금 구식으로 취급된다. 이는 '*(COMMON)'과 동등하다.

Input section과 garbage collection

link-time garbage collection이 사용중이면 ('--gc-sections'), 제거되지 말아야할 section들을 표시하는 것이 꽤 유용하다. 이는 KEEP(*(.init)) 이나 KEEP(SORT(*)(.ctors)) 처럼 input section의 wildcard entry를 KEEP()으로 둘러쌈으로써 할 수 있다.

Input section 예제

다음 예제는 완전한 linker script이다. 이는 linker에게 file 'all.o'로부터 모든 section들을 읽어서 그들을 ‘0x10000’위치에서 시작하는 output section 'outputa'의 시작에 두도록 이야기 한다. 그 바로 뒤에 'foo.o' file로부터의 section '.input1' 전부가 같은 output section내에 따른다. 'foo.o'로부터의 section '.input2' 전부는 output section 'outputb'내로 가고, 그 뒤에 'foo1.o'로부터의 section '.input1'이 따른다. 남은 다른 모든 파일로부터의 '.input1''.input2' section들 전부가 output section 'outputc'로 쓰여진다.

SECTIONS {
  outputa 0x10000 :
    {
    all.o
    foo.o (.input1)
    }
  outputb :
    {
    foo.o (.input2)
    foo1.o (.input1)
    }
  outputc :
    {
    *(.input1)
    *(.input2)
    }
}

Output section data

output section command로 output section 내에 BYTE, SHORT, LONG, QUAD 또는 SQUAD를 사용해서 byte 수를 정하는 data를 넣을 수 있다. 각 keyword 뒤에는 괄호 안에 저장할 값을 쓴 expression이 따른다 (Linker Scripts안의 Expression절을 봐라). expression의 값은 location counter의 현재 값에 저장된다.

BYTE, SHORT, LONG과 QUAD command는 1, 2, 4, 그리고 8 bytes를 (각각) 저장한다. Byte들을 저장하고 나서 location counter는 저장된 byte의 수만큼 증가한다.

예를 들어, 다음은 byte 1을 저장하고 symbol ‘addr’의 4 byte 값이 뒤에 온다:

BYTE(1)
LONG(addr)

64 bit host 또는 target을 사용하면 QUADSQUAD는 같다; 둘 다 8 byte 또는 64 bit 값을 저장한다. host와 target 모두 32 bit를 사용하면, expression은 32 bit로 계산된다. 이 경우 QUAD는 64 bit로 zero extended된 32 bit 값을 저장하고, SQUAD는 64 bit로 sign extended된 32 bit 값을 저장한다.

output file의 보통의 경우처럼 output file format이 endianness를 명시하면, 그 값은 그 endianness에 따라 저장된다. object file format이 endianness를 명시하지 않으면, 예를 들어 S-records가 true인 경우, 그 값은 첫 input object file의 endianness에 따라 저장된다.

현재 section의 fill pattern을 설정하려면 FILL command를 사용할 수 있다. 이것 뒤에는 괄호 안의 expression이 온다. 그 section 내의 다른 모든 지정되지 않은 memory regions (예를 들어, input sections에 요구되는 alignment로 인해 남는 gap들)은 그 expression의 two least significant bytes로 필요하면 반복하면서 채워진다. FILL statement는 그 section definition 내에 나타나는 지점 이후의 memory 위치에 적용된다. 하나 이상의 FILL statement를 넣으므로써 output section의 다른 부분에 다른 fill pattern을 쓸 수 있다.

다음 예제는 값 '0x9090'으로 지정되지 않는 메모리 region을 어떻게 채우는지를 보여준다.

FILL(0x9090)

FILL command는 ‘=fillexp‘ output section attribute와 비슷하지만 (Output section 채우기 절을 봐라), 전체 section이 아닌, FILL command 다음의 section 부분에만 영향을 미친다. 둘 다 사용되면 FILL command가 우선된다.

Output section keywords

output section commands처럼 쓸 수 있는 두가지 keyword가 있다.

CREATE_OBJECT_SYMBOLS

이 command는 linker에게 각 input file을 위한 symbol을 만들라고 이야기한다. 각 symbol의 이름은 해당하는 input file의 이름이 된다. 각 symbol의 section은 CREATE_OBJECT_SYMBOLS command가 있는 그 output section이 된다. 이는 a.out object file format에 편리하다. 다른 object file에서는 거의 사용되지 않을 것이다.

CONSTRUCTORS

a.out object file format을 사용해서 linking할 때 linker는 C++ global constructors와 destructors를 지원하기 위해 unusual set construct를 사용한다. ECOFF나 XCOFF 같은 arbitrary section을 지원하지 않는 object file format들을 linking할 때 linker는 알아서 그 이름으로 C++ global constructors와 destructors를 인식한다. 이들 object file format들을 위해서 CONSTRUCTORS command가 linker에게 CONSTRUCTORS command가 나오는 output section 내에 constructor information을 두도록 알려준다. CONSTRUCTORS command는 다른 object file format들에서는 무시된다. symbol __CTOR_LIST__ 는 global constructors의 시작을 표시하고, symbol __DTOR_LIST는 그 끝을 표시한다. list 내의 첫번째 word는 entries의 수이고, 그 뒤에 각 consructor나 destructor의 address가, 그 뒤에 zero word가 하나 붙는다. compiler는 실제로 code를 실행하기 위헤서 조정을 해야 한다. 이들 object file format들을 위해 GNU C++은 보통 subroutine __main으로부터 constructors를 호출한다; __main의 호출은 자동으로 main을 위한 startup code안으로 들어간다. GNU C++은 보통 destructors를 atexit를 사용하거나 아니면 exit로부터 그 funtion을 직접 호출해서 실행한다. arbitrary section name을 지원하는 COFF나 ELF 같은 object file format들에는 GNU C++은 .ctors.dtors sections 안에 global constructors와 destructors의 addresses를 두도록 조정할 것이다. 다음 sequence를 linker script에 두는 것은 GNU C++ runtime code가 보는 table을 build할 것이다.

      __CTOR_LIST__ = .;
      LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
      *(.ctors)
      LONG(0)
      __CTOR_END__ = .;
      __DTOR_LIST__ = .;
      LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
      *(.dtors)
      LONG(0)
      __DTOR_END__ = .;

initialization priority에 global constructors가 실행되는 순서 상의 control을 제공하는 GNU C++ support를 사용하고 있다면, 반드시 정확한 순서로 그들이 실행됨을 보장하기 위해 link time에 constructors를 sort해야 한다. CONSTUCTORS command를 사용하려면, 대신 'SORT(CONSTRUCTORS)'를 사용하라. .ctors.dtors section을 사용하려면, 그냥 '*(.ctors)''*(.dtors)' 대신에 '*(SORT(.ctors))''*(SORT(.dtors))'를 사용하라. 보통 compiler와 linker는 이들 이슈를 알아서 처리할 것이고, 이에 대해 신경쓸 필요가 없을 것이다. 그러나 C++을 사용하고 linker scripts를 작성한다면 이를 고려할 필요가 있을 수 있다.

Output section discarding

linker는 아무 내용도 없는 output section을 만들지 않을 것이다. 이는 어떤 input files내에 있을 수 있는, 혹은 없을 수 있는 input section들을 참조하는데 편리하다. 예를 들어:

.foo { *(.foo) }

는 ‘.foo’ section이 적어도 하나의 input file내에 있을 때만 ‘.foo’ section을 output file 내에 만들거다.

input section description보다는 symbol assignment같은 output section command를 사용한다면, 맞는 input section들이 없어도 output section은 언제나 만들어 질 것이다.

special output section name ‘/DISCARD/’는 input sections을 discard할 때 사용될 수 있다. 이름이 ‘/DISCARD/’인 output section으로 assigned되는 모든 input section들은 output file내에 포함되지 않는다.

Output section attributes

다음처럼 생긴 output section의 full description을 위에서 보았다.

section [address] [(type)] : [AT(lma)]
  {
    output-section-command
    output-section-command
    ...
  } [>region] [:phdr :phdr ...] [=fillexp]

이미 section, address, output-section-command에 대해 설명했다. 이번 절에서는 남은 section attributes를 설명할 거다.

Output section type

각 output section은 type을 가질 수 있다. 이 type은 괄호 안의 keyword이다. 다음 type들이 정의된다:

NOLOAD

그 section은 not loadable로 표시되어야 한다. 그래서 프로그램이 실행될 때 memory로 loaded되지 않을 거다.

DSECT

COPY

INFO

OVERLAY

이들 type 이름들은 backward compatibility를 위해서 지원된다. 잘 쓰이진 않는다. 모두 같은 효과를 갖는다. 그 section은 not allocatable로 표시되어야 한다. 그래서 프로그램이 실행될 때 그 section을 위한 memory가 allocated되지 않는다.

linker는 보통 그에 map되는 input section에 기초해서 output section의 attributes를 set한다. section type을 사용해서 이를 override할 수 있다. 예를 들어, 아래의 script sample에서 ‘ROM’ section은 memory location ‘0’에 지정되고, 프로그램이 실행될 때 loaded될 필요는 없다. ‘ROM’ section의 내용은 linker output file내에 평소처럼 나올거다.

SECTIONS {
  ROM 0 (NOLOAD) : { ... }
  ...
}

Output section LMA

모든 section은 virtual address (VMA)와 load address (LMA)를 갖는다; 기본적인 개념 절을 봐라. output section description에 있을 수 있는 address expression은 VMA를 set한다 (Output section address절을 봐라).

linker는 보통 LMA를 VMA와 같도록 set한다. 이를 AT keyword를 사용해서 바꿀 수 있다. AT keyword 뒤의 expression lma는 그 section의 load address를 지정한다.

이 기능은 ROM image를 쉽게 build하도록 design되었다. 예를 들어, 다음 linker script는 3 output section을 만든다: 0x1000에서 시작하는 '.text', VMA는 0x2000에서 시작하지만 '.text' section의 끝에 load되는 '.mdata', 그리고, address 0x3000에 uninitialized data를 잡는 '.bss'. symbol _data0x2000으로 정의되는데 이는 location counter가 LMA 값이 아닌 VMA 값을 담는다는 것을 보여준다.

SECTIONS
  {
  .text 0x1000 : { *(.text) _etext = . ; }
  .mdata 0x2000 : 
    AT ( ADDR (.text) + SIZEOF (.text) )
    { _data = . ; *(.data); _edata = . ;  }
  .bss 0x3000 :
    { _bstart = . ;  *(.bss) *(COMMON) ; _bend = . ;}
}

이 linker script로 generate되는 프로그램을 위한 실제 run-time initialization code는 ROM image로부터 initialized data를 그 runtime address로 복사하기 위해서 다음과 같은 것을 포함할거다. 이 코드가 linker script가 define하는 symbol들을 어떻게 유용하게 쓰는지에 주목하라.

extern char _etext, _data, _edata, _bstart, _bend;
char *src = &_etext;
char *dst = &_data;

/* ROM has data at end of text; copy it. */
while (dst < &_edata) {
  *dst++ = *src++;
}

/* Zero bss */
for (dst = &_bstart; dst< &_bend; dst++)
  *dst = 0;

Output section region

'>region'을 사용해서 한 section을 이전에 정의된 memory region으로 assign할 수 있다. MEMORY command 절을 봐라.

여기 간단한 예제다:

MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }

Output section phdr

':phdr'을 사용해서 한 section을 이전에 define된 program segment로 assign할 수 있다. PHDRS command 절을 봐라. 한 섹션이 하나 또는 그 이상의 segments로 assign되면, 명시적으로 :phdr modifier가 사용되기 전까지 모든 subsequent allocated sections들이 그들 segments로 assign될 것이다. :NONE으로 linker에게 모든 segment에 그 section을 두지 않을 것임을 알려줄 수 있다.

여기 간단한 예제다:

PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }

Output section fill

'=fillexp'를 사용해서 전체 section을 위한 fill pattern을 set할 수 있다. fillexp는 expression이다 (Linker Scripts에서 Expression절을 봐라). 그 output section 내의 memory의 모든 unspecified regions (예를 들어, input sections의 required alignment로 인해 남는 gaps) 은 그 값의 2 least significant bytes로 필요하면 반복적으로 채워질거다.

output section commands내에 FILL command로 fill value를 바꿀 수 있다; Output section data 절을 봐라.

여기 간단한 예제다:

SECTIONS { .text : { *(.text) } =0x9090 }

Overlay description

overlay description은 single memory image의 일부로 load되지만 같은 memory address에서 실행되는 section들을 describe하는 쉬운 방법을 제공한다. run time에 어떤 overlay manager가 overlaid sections를 요구사항에 맞게 아마 간단한 addressing bit를 조작해서 runtime memory address의 안과 밖에 copy할 거다. 이 방식은 예를 들면 특정 memory region이 다른 곳보다 빠를 때 유용할 수 있다.

Overlays는 OVERLAY command를 사용해서 describe한다. OVERLAY comand는 output section description처럼 SECTIONS command 안에서 사용된다. OVERLAY command의 full syntax는 다음과 같다:

OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]
  {
    secname1
      {
        output-section-command
        output-section-command
        ...
      } [:phdr...] [=fill]
    secname2
      {
        output-section-command
        output-section-command
        ...
      } [:phdr...] [=fill]
    ...
  } [>region] [:phdr...] [=fill]

OVERLAY (키워드) 를 빼고 모두 옵션이고, 각 section은 name (위에서 secname1secname2)을 가져야 한다. OVERLAY construct 내의 section definitions은 OVERLAY 내에 section들을 위해 define되는 address와 memory regions들이 없다는 것을 제외하면 일반적인 SECTIONS contruct (SECTIONS command를 봐라)내의 것들과 동일하다.

그 sections들은 모두 같은 starting address로 defined된다. 그 sections들의 load addresses는 전체에 OVERLAY를 위해 사용되는 load address에서 시작하는 memory 내에 consecutive하게 조정된다 (보통 section definitions처럼 load address는 옵션이고 default는 start address이다; start address 또한 옵션이고, default는 현재 location counter 값이다).

NOCROSSREFS keyword가 사용되면 sections 사이에 어떤 reference가 있다면, linker는 error로 report할 거다. 같은 address에서 section 모두가 실행되면, 보통 한 section이 다른 section에 직접 refer하는 것은 부적절하다. 다른 linker script command 절을 봐라.

OVERLAY 내의 각 모든 section에 대해 linker는 자동으로 2개의 symbol을 define한다. symbol __load_start_secname이 그 section의 starting load address로 defined된다. symbol __load_stop_secname이 그 section의 final load address로 defined된다. C identifiers내에 있지 않은 secname 내의 모든 문자는 제거된다. C (또는 assembler) code는 이들 symbol을 필요에 따라 overlaid section 근처로 옮기는데 사용할 수 있다.

 

 

 

 

 

출처: Using LD, the GUN linker – Linker Scripts

[Linux:Kernel] 왜 “volatile” 타입 클래스가 쓰이지 않는 것이 좋을까

이 문서의 저작권은 GPL 라이센스를 따릅니다(This document is released under the GPL license).
Documentation/volatile-considered-harmful.txt
번역: 양정석<dasomoli@gmailREMOVETHIS.com>
왜 “volatile” 타입 클래스가 쓰이지 않는 것이 좋은가
—————————————————
C 프로그래머들은 종종 volatile을 그 변수가 현재 스레드의 실행 밖에서 바뀔 수
있다는 의미로 사용합니다; 그 결과, 그들은 가끔 커널 코드 안에서 공유 자료
구조들을 사용할 때 이를 사용하고자 합니다. 다른 말로 하면, 그들은 간단한
atomic 변수의 일종처럼, 그게 아닌데도, volatile 형식을 다루는 것으로 알고
있습니다. volatile 커널 코드의 용도는 거의 절대 맞지 않습니다; 이 문서는 그
이유를 설명합니다.
volatile에 대한 이해를 위한 핵심은 그 목적이 거의 아무도 절대 진정으로 원하지
않는 최적화를 막기 위해서라는 것입니다. 커널 안에서, 누군가는 공유되는
자료 구조를 매우 다른 태스크의 원치 않는 동시 접근으로부터 보호해야 합니다.
원치 않는 동시성에 대한 보호 처리는 또한 거의 모든 최적화에 관련된 문제를 더
효율적인 방법으로 피할 것입니다.
volatile처럼, 자료로 안전하게 동시 접근을 하는 커널 기본 요소(스핀락(spinlock),
뮤텍스(mutex), 메모리 장벽(memory barrier), 기타 등등)는 원치 않는 최적화를
막도록 설계되었습니다. 그들이 적절히 사용된다면, 그들은 volatile을 사용할
필요가 없을 것입니다. volatile이 여전히 필요하다면, 거기에는 거의 확실히 코드
어딘가에 버그가 있습니다. 적절히 작성된 커널 코드 안에서 volatile은 오직
느리게만 만들 것입니다.
일반적인 커널 코드 블록을 생각해 봅시다:
    spin_lock(&the_lock);
    do_something_on(&shared_data);
    do_something_else_with(&shared_data);
    spin_unlock(&the_lock);
모든 코드가 락킹(locking) 규칙을 따른다면, shared_data의 값은 the_lock이 잡힌
동안, 예기치 않게 바뀔 수 없습니다. 그 자료를 가지고 놀고 싶어 할 어떤 다른
코드는 락 상에서 대기할 것입니다. 스핀락 기본 요소는 자료 접근이 그들을
가로지르는 최적화가 되지 않을 것임을 의미하는 메모리 장벽처럼 – 그들은
명시적으로 그렇게 하도록 작성되었습니다 – 동작합니다. 그래서 컴파일러는 그것이
shared_data 안에 있을 것을 안다고 생각할 수도 있지만. spin_lock() 호출이 메모리
장벽처럼 동작한 이후에는, 컴파일러가 알고 있는 어떠한 것도 잊어버리도록 강제할
것입니다. 거기에는 그 자료로의 접근에 대한 최적화 문제는 없을 것입니다.
shared_data가 volatile로 선언되었어도, 락킹은 여전히 필요할 것입니다.
그러나 컴파일러 역시, 우리가 다른 누구도 그것으로 무언가 할 수 없음을 알 때,
크리티컬 섹션 _안에서는_ shared_data로의 접근에 대한 최적화가 막혔으면 할
것입니다. 락이 잡힌 동안, shared_data는 volatile이 아닙니다. 공유된 자료로
뭔가 할 때, 적절한 락킹은 – 잠재적으로 해로운 – volatile을 불필요하게 만들 것
입니다.
volatile 저장 클래스는 근본적으로 메모리-매핑된(memory-mapped) I/O 레지스터들을
위해 의미가 있습니다. 커널 안에서 레지스터 접근 역시 락으로 보호돼야 합니다만,
누군가는 또한 컴파일러가 크리티컬 섹션 안에서 레지스터 접근을 “최적화하는 것”을
원하지 않습니다. 그러나 커널 안에서, I/O 메모리 접근은 언제나
접근자(accessor) 함수를 통해 이루어집니다; I/O 메모리에 포인터를 통해 직접
접근하는 것은 얼굴이 찡그려지고, 모든 아키텍처에서 작동하지 않습니다.
이들 접근자는 원치 않는 최적화를 막도록 작성되었습니다. 그래서, 역시,
volatile은 불필요합니다.
누군가가 volatile을 사용하고자 하는 또 다른 상황은 프로세서가 한 변수의 값을
위해 바쁜-기다림(busy-waiting)을 할 때입니다. 바쁜 기다림을 수행하는 옳은
방법은:
    while (my_variable != what_i_want)
        cpu_relax();
cpu_relax() 호출은 CPU 전력 소비를 낮출 수 있고, 하이퍼 스레드 된 다른
프로세서에 양보할 수 있습니다; 또한 컴파일러 장벽처럼 서비스하는 일이
일어납니다. 그래서, 역시, volatile은 불필요합니다. 물론, 바쁜 기다림은
일반적으로 시작하는 반사회적 행동입니다.
volatile이 커널 안에서 이해되는 적은 수의 드문 상황들이 여전히 있습니다:
  – 직접 I/O 메모리 접근이 동작하는 아키텍처에서는 위에서 설명된 접근자
    함수들이 volatile을 사용할 수 있습니다. 기본적으로, 각 접근자 호출은
    그 자체로 작은 크리티컬 섹션이 되고, 접근이 프로그래머가 의도한 대로
    일어남을 보장합니다.
  – 메모리를 변경하는, 그렇지만 다른 보이는 부수 효과(side effect)나, GCC에 의해
    제거되는 위험 없는 인라인 어셈블리 코드. volatile 키워드를 asm 문장에
    추가하는 것은 이 제거를 막을 것입니다.
  – jiffies 변수는 그것이 참조되는 순간마다 다른 값을 가지지만, 어떤 특별한
    락킹도 없이 읽힐 수 있어서 특별합니다. 그래서 jiffies는 volatile일 수
    있지만, 이 타입의 다른 변수들의 추가는 강하게 얼굴이 찌푸려집니다.
    jiffies는 이런 점에서 (리누스의 말로) “멍청한 유산” 이슈로 생각됩니다; 이를
    고치는 것은 그 가치보다 더 많은 문제를 일으킬 수 있습니다.
  – 가끔 합법적으로 volatile이 될 수 있는 I/O 장치에 의해 수정될 수 있는
    일관적인(coherent) 메모리 안의 자료 구조로의 포인터. 네트워크 어댑터가 어떤
    기술자(descriptor)가 처리되었는지를 나타내기 위해 포인터를 수정하는 데서,
    그 어댑터에 의해 사용되는 링 버퍼가 이런 종류 상황의 한 예제입니다.
코드 대부분에서, volatile의 적용을 위한 위의 타당한 이유가 없습니다. 그 결과,
volatile의 사용은 버그처럼 보이고, 코드에 추가적인 정밀 조사를 가져오도록 할
가능성이 큽니다. volatile을 사용하고자 하는 개발자들은 발걸음을 돌리고 그들이
정말로 하고자 하는 바에 대해서 생각해 봐야 할 것입니다.
volatile 변수들의 제거 패치는 – 그들이 동시성 이슈들을 적절히 생각했음을
보여주는 타당한 이유로 다가오는 만큼 – 일반적으로 환영받습니다.
참고
—-
[1] http://lwn.net/Articles/233481/
[2] http://lwn.net/Articles/233482/
언급해야 할 이름
—————-
원래의 자극과 연구는 Randy Dunlap에 의해
작성은 Jonathan Corbet에 의해
의견을 통한 개선은 Satyam Sharma, Johannes Stezenbach, Jesper
Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn, and Stefan
Richter로부터.
한국어 번역은 양정석이.