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 int
s and float
s.
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
'program tip' 카테고리의 다른 글
새 버전의 jQuery에서 가장 기대하는 것은 무엇입니까? (0) | 2021.01.09 |
---|---|
목록을 열로 감싸기 (0) | 2021.01.08 |
변수 이름을 문자열로 변환 하시겠습니까? (0) | 2021.01.08 |
MySQL에서 결과 집합 반복 (0) | 2021.01.08 |
재귀 뮤텍스를 언제 사용합니까? (0) | 2021.01.08 |