program tip

x86, win32에서 빈 프로그램의 GCC 어셈블리 출력

radiobox 2021. 1. 8. 08:05
반응형

x86, win32에서 빈 프로그램의 GCC 어셈블리 출력


나는 빈 프로그램을 작성하여 스택 오버플로 코더를 괴롭히지 않습니다. 저는 gnu 도구 모음을 탐색하고 있습니다.

이제 다음은 나에게 너무 깊을 수 있지만 빈 프로그램을 계속하기 위해 C 컴파일러의 출력을 검사하기 시작했습니다. GNU가 소비하는 것입니다.

gcc version 4.4.0 (TDM-1 mingw32)

test.c :

int main()
{
    return 0;
}

gcc -S test.c

    .file   "test.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .text
.globl _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    call    ___main
    movl    $0, %eax
    leave
    ret 

여기서 무슨 일이 일어나는지 설명해 주시겠습니까? 그것을 이해하려는 나의 노력이 있습니다. 내가 사용하고 as매뉴얼 내 최소 86 ASM 지식 :

  • .file "test.c" 논리적 파일 이름에 대한 지시문입니다.
  • .def: "심볼 이름에 대한 디버깅 정보 정의 시작" 문서에 따름 . 기호 (함수 이름 / 변수)는 무엇이며 어떤 종류의 디버깅 정보입니까?
  • .scl: docs say "Storage class may flag whether a symbol is static or external" . 이것이 내가 C에서 아는 것과 동일한 정적외부 입니까? 그리고 그 '2'는 무엇입니까?
  • .type: "기호 테이블 항목의 유형 속성으로" 매개 변수를 저장합니다 . 단서가 없습니다.
  • .endef: 문제 없어요.
  • .text: 이제 이것은 문제가 있습니다. 섹션이라고 불리는 것으로 보이며 코드의 위치를 ​​읽었지만 문서는 나에게 너무 많이 말하지 않았습니다.
  • .globl "ld에 기호를 표시합니다." , 매뉴얼은 이것에 대해 아주 명확합니다.
  • _main: 이것은 내 주요 기능의 시작 주소 (?) 일 수 있습니다.
  • pushl_: 스택에 EBP를 배치하는 긴 (32 비트) 푸시
  • movl: 32 비트 이동. 의사 -C :EBP = ESP;
  • andl: 논리 AND. 의사 -C : ESP = -16 & ESP, 나는 이것의 요점이 무엇인지 정말로 알지 못합니다.
  • call: IP를 스택으로 푸시하고 (호출 된 프로 시저가 되돌아 갈 수 있도록)있는 곳에서 계속 __main합니다. (__main은 무엇입니까?)
  • movl:이 0은 코드 끝에서 반환하는 상수 여야합니다. MOV는이 0을 EAX에 배치합니다.
  • leave: ENTER 명령어 (?) 후에 스택을 복원합니다. 왜?
  • ret: 스택에 저장된 명령어 주소로 돌아갑니다.

도와 주셔서 감사합니다!


.file "test.c"

로 시작하는 명령. 어셈블러에 대한 지시문입니다. 이것은 단지 이것이 "file.c"라고 말하고, 그 정보는 exe의 디버깅 정보로 내보낼 수 있습니다.

.def ___main; .scl 2; .type 32; .endef

.def 지시문은 디버깅 기호를 정의합니다. scl 2는 스토리지 클래스 2 (외부 스토리지 클래스)를 의미합니다. 유형 32는이 sumbol이 함수임을 나타냅니다. 이 숫자는 pe-coff exe-format으로 정의됩니다.

___main은 gcc에 필요한 부트 스트랩을 처리하는 호출 된 함수입니다 (C ++ 정적 이니셜 라이저 실행 및 필요한 기타 하우스 키핑과 같은 작업을 수행합니다).

.text

텍스트 섹션 시작-여기에 코드가 있습니다.

.globl _main

_main 심볼을 전역으로 정의하여 링커 및 연결된 다른 모듈에 표시됩니다.

.def        _main;  .scl    2;      .type   32;     .endef

_main과 동일하게 _main이 함수임을 나타내는 디버깅 기호를 생성합니다. 디버거에서 사용할 수 있습니다.

_본관:

새 레이블을 시작합니다 (주소로 끝납니다). 위의 .globl 지시문은이 주소를 다른 엔티티에 표시합니다.

pushl       %ebp

Saves the old frame pointer(ebp register) on the stack (so it can be put back in place when this function ends)

movl        %esp, %ebp

Moves the stack pointer to the ebp register. ebp is often called the frame pointer, it points at the top of the stack values within the current "frame"(function usually), (referring to variables on the stack via ebp can help debuggers)

andl $-16, %esp

Ands the stack with fffffff0 which effectivly aligns it on a 16 byte boundary. Access to aligned values on the stack are much faster than if they were unaligned. All these preceding instructions are pretty much a standard function prologue.

call        ___main

Calls the ___main function which will do initializing stuff that gcc needs. Call will push the current instruction pointer on the stack and jump to the address of ___main

movl        $0, %eax

move 0 to the eax register,(the 0 in return 0;) the eax register is used to hold function return values for the stdcall calling convention.

leave

The leave instruction is pretty much shorthand for

movl     ebp,esp
popl     ebp

i.e. it "undos" the stuff done at the start of the function - restoring the frame pointer and stack to its former state.

ret

Returns to whoever called this function. It'll pop the instruction pointer from the stack (which a corresponding call instruction will have placed there) and jump there.


There's a very similar exercise outlined here: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax

You've figured out most of it -- I'll just make additional notes for emphasis and additions.

__main is a subroutine in the GNU standard library that takes care of various start-up initialization. It is not strictly necessary for C programs but is required just in case the C code is linking with C++.

_main is your main subroutine. As both _main and __main are code locations they have the same storage class and type. I've not yet dug up the definitions for .scl and .type yet. You may get some illumination by defining a few global variables.

The first three instructions are setting up a stack frame which is a technical term for the working storage of a subroutine -- local and temporary variables for the most part. Pushing ebp saves the base of the caller's stack frame. Putting esp into ebp sets the base of our stack frame. The andl aligns the stack frame to a 16 byte boundary just in case any local variables on the stack require 16 byte alignment (for the x86 SIMD instructions require that alignment, but alignment does speed up ordinary types such as ints and floats.

At this point you'd normally expect esp to get moved down in memory to allocate stack space for local variables. Your main has none so gcc doesn't bother.

The call to __main is special to the main entry point and won't typically appear in subroutines.

The rest goes as you surmised. Register eax is the place to put integer return codes in the binary spec. leave undoes the stack frame and ret goes back to the caller. In this case, the caller is the low-level C runtime which will do additional magic (like calling atexit() functions, set the exit code for the process and ask the operating system to terminate the process.


Regarding that andl $-16,%esp

  • 32 bits: -16 in decimal equals to 0xfffffff0 in hexadecimal representation
  • 64 bits: -16 in decimal equals to 0xfffffffffffffff0 in hexadecimal representation

So it will mask off the last 4 bits of ESP (btw: 2**4 equals to 16) and will retain all other bits (no matter if the target system is 32 or 64 bits).


Further to the andl $-16,%esp, this works because setting the low bits to zero will always adjust %esp down in value, and the stack grows downward on x86.


I don't have all answers but I can explain what I know.

ebp is used by the function to store the initial state of esp during its flow, a reference to where are the arguments passed to the function and where are its own local variables. The first thing a function does is to save the status of the given ebp doing pushl %ebp, it is vital to the function that make the call, and than replaces it by its own current stack position esp doing movl %esp, %ebp. Zeroing the last 4 bits of ebp at this point is GCC specific, I don't know why this compiler does that. It would work without doing it. Now finally we go into business, call ___main, who is __main? I don't know either... maybe more GCC specific procedures, and finally the only thing your main() does, set return value as 0 with movl $0, %eax and leave which is the same as doing movl %ebp, %esp; popl %ebp to restore ebp state, then ret to finish. ret pops eip and continue thread flow from that point, wherever it is (as its the main(), this ret probably leads to some kernel procedure which handles the end of the program).

Most of it is all about managing the stack. I wrote a detailed tutorial about how stack is used some time ago, it would be useful to explain why all those things are made. But its in portuguese...

ReferenceURL : https://stackoverflow.com/questions/1317081/gccs-assembly-output-of-an-empty-program-on-x86-win32

반응형