2010-06-07 4 views
2

일부 C 코드의 컴파일러 출력을보고 싶지만 간단한 프로그램을 C로 작성하고 gcc를 사용하여 어셈블리 파일을 생성했습니다.C 코드의 동등한 어셈블리 코드 디코딩

#include <stdio.h> 

int main() 
{ 
    int i = 0; 

    if (i == 0) 
    { 
     printf("testing\n"); 
    } 

    return 0; 
} 

그것에 대해 생성 된 조립체는 여기 (에만 주 기능) :

코드는 다음이다

_main: 
pushl %ebpz 
movl %esp, %ebp 
subl $24, %esp 
andl $-16, %esp 
movl $0, %eax 
addl $15, %eax 
addl $15, %eax 
shrl $4, %eax 
sall $4, %eax 
movl %eax, -8(%ebp) 
movl -8(%ebp), %eax 
call __alloca 
call ___main 
movl $0, -4(%ebp) 
cmpl $0, -4(%ebp) 
jne L2 
movl $LC0, (%esp) 
call _printf 
L2: 
movl $0, %eax 
leave 
ret 

I는 C 코드 및 어셈블리를 상관 절대 딱하다 암호. 코드에서 수행해야하는 모든 작업은 레지스터에 0을 저장하고 상수 0과 비교하여 적절한 조치를 취하는 것입니다. 그러나 회의에서 무슨 일이 벌어지고 있습니까?

+0

읽을 수 있도록 출력 인텔 구문을 설정해야합니다 ('-Mintel' IIRC). –

+0

AT & T 구문은 완벽하게 악의적이며 UNIX에서 작업하는 경우에는 이중 언어로만 읽을 것을 권장합니다. – caf

+0

투표가 너무 광범위하게 닫힙니다. –

답변

6

main은 특별한 기능이므로 다른 기능에서이 유형을 수행하면 더 좋은 결과를 얻을 수 있습니다 (바람직하게는 main이없는 자체 파일 임). 예 :

void foo(int x) { 
    if (x == 0) { 
     printf("testing\n"); 
    } 
} 

은 조립할 때 훨씬 분명합니다. 이렇게하면 최적화로 컴파일하고 조건부 동작을 계속 볼 수 있습니다. 최적화 레벨이 0 이상인 원본 프로그램을 컴파일한다면 컴파일러가 그 결과를 계산할 수 있기 때문에 아마도 비교를하지 못할 것입니다. 이 코드를 사용하면 컴파일러에서 매개 변수 부분 (x)을 숨겨서 컴파일러에서이 최적화를 수행 할 수 없습니다.

이 현재 함수의 스택 프레임을 설정하는 것입니다

_main: 
pushl %ebpz 
movl %esp, %ebp 
subl $24, %esp 
andl $-16, %esp 

여분의 물건이 실제로 무엇입니까. x86에서 스택 프레임은 스택 포인터의 값 (16, 32 또는 64 비트의 SP, ESP 또는 RSP)과 기본 포인터의 값 (BP, EBP 또는 RBP) 사이의 영역입니다. 이것은 아마도 지역 변수가 존재하지만 실제는 아니며 명시 적 스택 프레임은 대부분의 경우 선택 사항입니다. 그러나 alloca 및/또는 가변 길이 배열을 사용하면 그 사용이 필요합니다.

이 특정 스택 프레임 구조는 스택이 16 바이트 정렬되도록하기 때문에 main 기능과 다릅니다. ESP에서 뺄셈하면 스택 크기가 로컬 변수를 보유 할만큼 충분히 증가하고 andl은 0에서 15까지 효과적으로 뺍니다. 따라서 16 바이트가 정렬됩니다. 이 정렬은 스택이 워드 정렬뿐만 아니라 캐시 정렬을 시작하도록하는 것을 제외하면 과도한 것처럼 보입니다.

movl $0, %eax 
addl $15, %eax 
addl $15, %eax 
shrl $4, %eax 
sall $4, %eax 
movl %eax, -8(%ebp) 
movl -8(%ebp), %eax 
call __alloca 
call ___main 

나는이 모든 것이 무엇을 모른다. alloca은 스택 포인터의 값을 변경하여 스택 프레임 크기를 늘립니다.

movl $0, -4(%ebp) 
cmpl $0, -4(%ebp) 
jne L2 
movl $LC0, (%esp) 
call _printf 
L2: 
movl $0, %eax 

나는 이것이 무엇을하는지 생각합니다. 그렇지 않다면 call은 문자열의 주소를 스택의 최상위 위치로 옮겨서 printf에 의해 검색 될 수 있다고 생각하면 movl입니다. printf가 그 주소를 사용하여 printf의 다른 인수 (이 경우에는없는 경우)의 주소를 유추 할 수 있도록 스택에 전달되어야합니다.

leave 

이 명령은 이전에 말한 스택 프레임을 제거합니다. 본질적으로 movl %ebp, %esp이고 그 뒤에 popl %ebp이옵니다. 스택 프레임을 생성하는 데 사용할 수있는 enter 명령어도 있지만 gcc는 스택 프레임을 사용하지 않았습니다. 스택 프레임을 명시 적으로 사용하지 않을 경우 EBP을 일반 puropose 레지스터로 사용할 수 있으며 leave 대신 컴파일러에서 스택 프레임 크기를 스택 포인터에 추가하면 스택 크기가 프레임 크기만큼 감소합니다.

ret 

설명 할 필요가 없습니다.

당신이 최적화

난 당신이 다른 최적화 수준이 FO을 모두 다시 컴파일 확신

컴파일, 그래서 당신은 아마 이상한 찾을 것입니다 발생할 수 있습니다 뭔가를 지적 할 것이다 때.형식 문자열에 %이없고 추가 매개 변수가 전달되지 않은 경우 이 printffprintf을 각각 putsfputs으로 바꾼 것을 확인했습니다. 이것은 (많은 이유로) putsfputs으로 전화하는 것이 훨씬 저렴하고 결과적으로 당신이 원하는 것을 얻을 수 있기 때문입니다.

+0

로저 그. 정확히 내가 알고 싶었던 !!! – puffadder

+0

'__alloca' 호출은'alloca()'서브 시스템의 초기화 작업을하고 있습니다. – caf

+0

@caf : 그 의미에 대해 더 많은 정보를 제공해 주시겠습니까? – nategoose

1

C 컴파일러가 어셈블리 된 어셈블리를 이해하려면 어셈블리 언어에 대한 지식이 필요합니다.

tutorial

+0

기본 어셈블리 구문을 알고 있지만 ... 어셈블리 코드가 왜 그렇게 많은 일을하는지 이해하고 싶습니다 ... – puffadder

3

은 프리앰블/꼬리말에 대해 걱정하지 마십시오 도움이 될 수 있습니다 - 당신이 관심있는 부분은 다음과 같습니다

movl $0, -4(%ebp) 
cmpl $0, -4(%ebp) 
jne L2 
movl $LC0, (%esp) 
call _printf 
L2: 

그것은 꽤 자명 한 방법이에 관해서는해야한다 원본 C 코드와 상호 연관됩니다.

+0

무엇이 preamble이고 postamble입니까? 왜 컴파일러가 그것을 처음에 넣어야합니까? – puffadder

+0

main() 함수와 관련된 오버 헤드 때문에 – Mawg

+3

@puffadder : 프리앰블/포스트 앰블은 스택과 다양한 레지스터를 설정하기위한 일반적인 보일러 플레이트 코드 일뿐입니다. main은 일반적으로 두 개의 매개 변수를 사용하고 함수 결과를 반환한다는 것을 기억하십시오. –

2

첫 번째 부분은 간단한 초기화 코드로 간단한 예제의 경우에는 이해가되지 않습니다. 이 코드는 최적화 플래그로 제거됩니다.

마지막 부분은 C 코드로 맵핑 될 수

movl $0, -4(%ebp) // put 0 into variable i (located at -4(%ebp)) 
cmpl $0, -4(%ebp) // compare variable i with value 0 
jne L2     // if they are not equal, skip to after the printf call 
movl $LC0, (%esp) // put the address of "testing\n" at the top of the stack 
call _printf   // do call printf 
L2: 
movl $0, %eax  // return 0 (calling convention: %eax has the return code) 
2

음을, 그 많은 기능과 관련된 오버 헤드이다. main()은 다른 것과 마찬가지로 함수이므로 시작시 스택에 반환 주소를 저장하고 마지막에 반환 값을 설정해야합니다.

혼합 소스를 생성하려면 GCC를 사용하는 것이 좋습니다. 코드 및 각 어셈블러에 대해 생성 된 어셈블러를 표시하는 어셈블러가 있습니다. 당신이 변환 된 어셈블리와 함께 C 코드를보고 싶다면

이 같은 명령 줄을 사용하십시오

gcc -c -g -Wa,-a,-ad [other GCC options] foo.c > foo.lst 

그냥 GCC를 사용하여 리눅스에 http://www.delorie.com/djgpp/v2faq/faq8_20.html

참조하십시오. Cygwin에서 http://www.cygwin.com/


편집 부하 아래로 Windows에서 - 또한 Using GCC to produce readable assembly?

1

http://oprofile.sourceforge.net/doc/opannotate.htmlhere 자세한 내용을 참조하십시오이 질문을 참조하십시오. 더 나은 이해를 위해 C 주석을 사용하여 어셈블리 코드를 생성 할 수 있습니다.

gcc -g -Wa,-adhls your_c_file.c > you_asm_file.s 

약간의 도움이 될 것입니다.