2016-12-18 3 views
4

우리는 간단한 코드를 가정 해 봅시다. this answer에서 변수는 프로그램 파일 내의 초기화 된 데이터 세그먼트 (.data) 에 저장되지만 포인터이므로 값 (힙 세그먼트의 주소)은 런타임에 결정됩니다. 그렇다면 프로그램 파일 내의 데이터 세그먼트에 저장된 값은 무엇입니까?전역 포인터 변수가 메모리에 저장되는 방법은 무엇입니까? 변수 <code>q</code> 글로벌 및 초기화, 분명히</p> <pre><code>int* q = new int(13); int main() { return 0; } </code></pre> <p>:

내 시도 : 내 생각에
는, 컴파일러는 더 의미있는 값
세그먼트 데이터 변수 q (64 비트 주소 일반적으로 8 바이트)를위한 공간을 할당합니다. 그런 다음 텍스트 세그먼트에 초기화 코드를 넣고 main 함수 코드 앞에 q 변수를 런타임에 초기화합니다. 이 같은 어셈블리에서 :

 .... 
    mov edi, 4 
    call operator new(unsigned long) 
    mov DWORD PTR [rax], 13 // rax: 64 bit address (pointer value) 

    // offset : q variable offset in data segment, calculated by compiler 
    mov QWORD PTR [ds+offset], rax // store address in data segment 
    .... 
main: 
    .... 

어떤 생각?

+1

'gcc '로 링크 할 때, 디폴트 시작점은'_start'입니다. 이 초기화가 코드를 가지고있는 곳이며,'main'을 호출합니다. 따라서 clib없이 어셈블리 프로그래밍을하고 있지만 gcc와 연결하는 사람들은 자신의 코드 시작 부분에'_start :'라벨을 붙여야 만합니다. 반면에 기본 clib와 연결된 사람들은'main :'에서 시작합니다 (소스에서 바이너리가 시작됩니다 lib에서'_start :'에). :) – Ped7g

+1

그것에 대해 자세히 설명하기 위해'_start'는 함수가 아니므로 C 또는 C++에서'_start'를 쓸 수 없습니다. 어셈블리에서는 함수를 작성할 필요가 없으며 임의 코드를 작성할 수 있으므로'_start'를 직접 작성할 수 있습니다. –

+0

감사합니다. Ped7g & Dietrich Epp. –

답변

3

예, 그것이 본질적으로 어떻게 작동하는지입니다.

ELF .data에서 .bss.text은 세그먼트가 아니라 실제로 섹션입니다. 당신은 당신의 컴파일러를 실행하여 어셈블리를 직접 볼 수 있습니다 : 당신은 일반적으로 main 기능, 그 함수 외부 초기화 코드의 어떤 종류를 볼 수

c++ -S -O2 test.cpp 

. 프로그램 진입 점 (C++ 런타임의 일부)은 초기화 코드를 호출 한 다음 main을 호출합니다. 초기화 코드는 생성자와 같은 것을 실행하는 책임이 있습니다. 그것은 단지 상수가 아닌 초기화에 의해 실행시에 초기화 이후

+0

감사합니다. 곧 세그먼트와 섹션의 차이점을 말씀해 주시겠습니까? –

+2

섹션은 오브젝트 파일에서 2 진을 생성하기 위해 링크 타임에 사용됩니다. 세그먼트는 런타임시 이진을 메모리로로드하는 데 사용됩니다. 세그먼트에는 이름이 없습니다. –

+0

@DietrichEpp : 텍스트 세그먼트 (링커가'.text','.rodata' 및 여러 가지 다른 것들을 넣는 곳) 또는 데이터 세그먼트 ('.data') 나 BSS에 관해 이야기하는 것은 꽤 표준 적입니다. 필자는 이것이 ELF의 공식적인 부분이 아니라고 생각합니다. 왜냐하면'readelf -a' 결과는 섹션 - 세그먼트 매핑에서 번호가 매겨진 세그먼트 만 보여주기 때문입니다. –

2

int *q이의 .bss에하지 .data 섹션을 갈 것이다 (그래서이없는 C에, C++에서만 합법적이다). 실행 파일의 데이터 세그먼트에 8 바이트를 가질 필요가 없습니다.

컴파일러는 main을 호출하기 전에 CRT (C 런타임) 시작 코드가 호출하는 이니셜 라이저 배열에 해당 주소를 넣어서 이니셜 라이저 함수가 실행되도록 정렬합니다.

Godbolt 컴파일러 탐색기에서 지시문의 모든 잡음없이 init 함수의 asm을 볼 수 있습니다. 주소 지정 모드는 q에 대한 단순한 RIP 관련 액세스 일뿐입니다. 링커는 .text 및섹션이 개별 세그먼트로 끝나더라도 링크 시점 상수이므로 RIP에서 오른쪽 오프셋을 채 웁니다.

Godbolt의 compiler-noise filtering은 우리에게 적합하지 않습니다. 지시어 중 일부는 적합하지만 대부분은 관련이 없습니다. 아래는 손으로 선택한 gcc6.2 -O3 asm output with Godbolt's "filter directives" option unchecked의 조합입니다 (int* q = new int(13); 진술에만 해당). (main을 동시에 컴파일 할 필요가 없으며 실행 파일을 링크하지 않습니다.)

# gcc6.2 -O3 output 
_GLOBAL__sub_I_q:  # presumably stands for subroutine 
    sub  rsp, 8   # align the stack for calling another function 
    mov  edi, 4   # 4 bytes 
    call operator new(unsigned long) # this is the demangled name, like from objdump -dC 
    mov  DWORD PTR [rax], 13 
    mov  QWORD PTR q[rip], rax  # clang uses the equivalent `[rip + q]` 
    add  rsp, 8 
    ret 

    .globl q 
    .bss 
q: 
    .zero 8  # reserve 8 bytes in the BSS 

ELF 데이터 (또는 기타) 세그먼트의 기준에 대한 언급이 없습니다.

세그먼트 레지스터 무시가 없습니다. ELF 세그먼트는 x86 세그먼트와 아무 관련이 없습니다. (이 경우 기본 세그먼트 레지스터는 DS이므로 컴파일러에서 [ds:rip+q] 등을 내 보내지 않아도됩니다. 지시어에 세그먼트 무시 접두사가 없더라도 일부 디스어셈블러는 명시 적이며 DS를 표시 할 수 있습니다.)

컴파일러는 호출 할 주선 방법

이다 main() 전 :

브라운관 시작 코드는 .init_array 섹션의 크기를 알고 메모리 간접 call 명령을 사용하는 루프를 가지고
# the "aw" sets options/flags for this section to tell the linker about it. 
    .section  .init_array,"aw" 
    .align 8 
    .quad _GLOBAL__sub_I_q  # this assembles to the absolute address of the function. 

각 기능에 대해 r 차례로.

.init_array 섹션은 쓰기 가능으로 표시되어 있으므로 데이터 세그먼트로 이동합니다. 나는 그것을 쓰는 것이 확실하지 않다. 어쩌면 CRT 코드는 포인터를 호출 한 후에 포인터를 0으로 설정하여 이미 완료했다고 표시 할 수 있습니까?


동적 링크를 수행하는 동안 ELF 인터프리터에 의해 수행되는 동적 라이브러리에서 초기화를 실행하기위한 리눅스에서 유사한 메커니즘이있다. 따라서 printf() 또는 기타 glibc stdio 함수를 직접 작성한 asm으로 만든 동적 연결 바이너리에서 _start으로 호출 할 수 있으며, 올바른 init 함수를 호출하지 않으면 왜 정적으로 링크 된 바이너리에서 실패하는지 이유가 여기 있습니다. 자신의 _start 또는 단지 main()을 정의하는 정적 또는 동적 바이너리를 빌드하는 방법에 대한 자세한 내용은 this Q&A을 참조하십시오 (libc 포함 또는 제외).

+0

포인터 변수를 &와 함께 사용하여 코드를 컴파일했습니다. 결과는'data' 섹션 크기가 변경되었습니다. 'size' 명령을 사용하여 확인했습니다. 그래서'data' 섹션에 저장해야합니다. –

+0

@MehranTorki : 어떤 컴파일러로? 64 또는 32 비트 코드? 어떤 OS인가? 'q'가 전역 적이기 때문에, 아마도 여분의'.data' 크기가 GOT 또는 무엇인가에 사용됩니까? 필자가 예상했던 asm 출력에서 ​​포인터 자체는'.bss'에 확실히 저장되어 있습니다. 컴파일시에'new'를 평가할 수없고 런타임 이니셜 라이저를 피할 수 없다면, 어떤 컴파일러도'.data'에 넣을 수 없다는 것을 의미합니다.그러나 그것은'delete' 가능 포인터가 실행 파일에 임베딩되어 컴파일러가 libstdC++의 내부 동작에 의존해야한다는 것을 의미합니다. –

+1

@MehranTorki : 방금 해보 았습니다. 빈 .cpp는 0/0/0 test/data/bss로 .o로 컴파일됩니다. gcc5.2 -O3는'int * q = new int (13);'을'size '가 텍스트 세그먼트에 80 바이트, 데이터 세그먼트에 8 바이트, bss에 8 바이트라고하는 .o로 컴파일합니다. bss의 8 바이트는'objdump -D' ('.bss' 섹션의 일부로 심볼을 보여줍니다)에서 볼 수 있듯이'q'를위한 것입니다. 'readelf -a'는 이것을 확인합니다.'q'는 섹션 번호 3에 있고,'.bss'입니다. 나는'.data'에 들어가는 것이 무엇인지 알지 못했지만, 그것은'q' 자체의 저장 공간이 아닙니다. –